Decompiled source of RunicCharacterVault v1.0.2

RunicCharacterVault.dll

Decompiled 4 days ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using PartyCSharpSDK;
using PlayFab;
using PlayFab.Party;
using RunicCharacterVault.Shared;
using Splatform;
using TMPro;
using UnityEngine;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("Runic Character Vault")]
[assembly: AssemblyDescription("Server-authoritative Valheim character storage with durable backups and admission protection")]
[assembly: AssemblyCompany("Chazman")]
[assembly: AssemblyProduct("Runic Character Vault")]
[assembly: AssemblyFileVersion("1.0.2.0")]
[assembly: AssemblyInformationalVersion("1.0.2")]
[assembly: InternalsVisibleTo("RunicCharacterVault.Tests")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyVersion("1.0.2.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 RunicCharacterVault
{
	internal static class BackupRetention
	{
		private sealed class BackupFile
		{
			internal string Path { get; }

			internal DateTime Timestamp { get; }

			internal BackupFile(string path, DateTime timestamp)
			{
				Path = path;
				Timestamp = timestamp;
			}
		}

		private const int RecentBackupCount = 5;

		private const int DailyBackupCount = 10;

		private const string TimestampFormat = "yyyyMMdd'T'HHmmssfffffff'Z'";

		internal static IReadOnlyList<string> Apply(string directory, string profileName)
		{
			List<BackupFile> source = (from backup in FindBackups(directory, profileName)
				orderby backup.Timestamp descending
				select backup).ToList();
			HashSet<string> retained = new HashSet<string>(from backup in source.Take(5)
				select backup.Path, StringComparer.Ordinal);
			DateTime dailyBoundary = (from backup in source.Take(5)
				select backup.Timestamp.Date).DefaultIfEmpty(DateTime.MinValue).Last();
			IEnumerable<BackupFile> source2 = from @group in (from backup in source.Skip(5)
					where backup.Timestamp.Date < dailyBoundary
					group backup by backup.Timestamp.Date into @group
					orderby @group.Key descending
					select @group).Take(10)
				select @group.OrderBy((BackupFile backup) => backup.Timestamp).First();
			retained.UnionWith(source2.Select((BackupFile backup) => backup.Path));
			List<string> list = new List<string>();
			foreach (BackupFile item in source.Where((BackupFile backup) => !retained.Contains(backup.Path)))
			{
				File.Delete(item.Path);
				list.Add(Path.GetFileName(item.Path));
			}
			return list;
		}

		private static IEnumerable<BackupFile> FindBackups(string directory, string profileName)
		{
			string prefix = profileName + "_";
			string[] files = Directory.GetFiles(directory, "*.fch");
			foreach (string path in files)
			{
				string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(path);
				if (fileNameWithoutExtension.StartsWith(prefix, StringComparison.Ordinal) && DateTime.TryParseExact(fileNameWithoutExtension.Substring(prefix.Length), "yyyyMMdd'T'HHmmssfffffff'Z'", CultureInfo.InvariantCulture, DateTimeStyles.AdjustToUniversal | DateTimeStyles.AssumeUniversal, out var result))
				{
					yield return new BackupFile(path, result);
				}
			}
		}
	}
	internal interface ICharacterProfileCatalog
	{
		bool HasProfile(string accountId);
	}
	internal enum CharacterAdmission
	{
		ExistingProfile,
		NewEnrollment,
		RejectUnregisteredProfile,
		RejectAdditionalCharacter,
		RejectConcurrentEnrollment
	}
	internal static class CharacterAdmissionPolicy
	{
		internal static CharacterAdmission Decide(bool hasStoredProfile, bool createdThisSession, bool allowMultipleCharacters, bool accountHasProfile, bool enrollmentAvailable, bool allowExistingCharacters = false)
		{
			if (hasStoredProfile)
			{
				return CharacterAdmission.ExistingProfile;
			}
			if (!createdThisSession && !allowExistingCharacters)
			{
				return CharacterAdmission.RejectUnregisteredProfile;
			}
			if (!allowMultipleCharacters && accountHasProfile)
			{
				return CharacterAdmission.RejectAdditionalCharacter;
			}
			if (!enrollmentAvailable)
			{
				return CharacterAdmission.RejectConcurrentEnrollment;
			}
			return CharacterAdmission.NewEnrollment;
		}
	}
	internal static class CharacterAdmissionMessages
	{
		internal static string ForRejection(CharacterAdmission admission, IReadOnlyList<string> existingProfileNames)
		{
			if (admission == CharacterAdmission.RejectAdditionalCharacter && existingProfileNames != null && existingProfileNames.Count > 0)
			{
				string text = string.Join(", ", existingProfileNames.Select((string name) => name.ToUpperInvariant()));
				if (existingProfileNames.Count != 1)
				{
					return "You already have characters: " + text + ". You cannot create more.";
				}
				return "You already have a character: " + text + ". You cannot create more.";
			}
			if (admission != CharacterAdmission.RejectUnregisteredProfile)
			{
				return "This platform account already has a character.";
			}
			if (existingProfileNames == null || existingProfileNames.Count == 0)
			{
				return "This server has not imported your character. Ask its administrator to enable AllowExistingCharacters in Character Vault. Your character has not been reset.";
			}
			return ((existingProfileNames.Count == 1) ? "Create a new character or use the previously used one: " : "Create a new character or use one of the previously used ones: ") + string.Join(", ", existingProfileNames.Select((string name) => name.ToUpperInvariant())) + ".";
		}
	}
	internal sealed class CharacterAdmissionEvaluator
	{
		private readonly ICharacterProfileCatalog _profiles;

		internal CharacterAdmissionEvaluator(ICharacterProfileCatalog profiles)
		{
			_profiles = profiles;
		}

		internal CharacterAdmission Decide(bool hasStoredProfile, string accountId, bool createdThisSession, bool allowMultipleCharacters, bool enrollmentAvailable, bool allowExistingCharacters = false)
		{
			bool accountHasProfile = !hasStoredProfile && !allowMultipleCharacters && _profiles.HasProfile(accountId);
			return CharacterAdmissionPolicy.Decide(hasStoredProfile, createdThisSession, allowMultipleCharacters, accountHasProfile, enrollmentAvailable, allowExistingCharacters);
		}
	}
	internal sealed class ServerProfileSessionState
	{
		internal bool CanSave
		{
			get
			{
				if (Verified && Admitted)
				{
					return Permitted;
				}
				return false;
			}
		}

		internal bool PermissionChecked { get; private set; }

		internal bool Verified { get; set; }

		internal bool Admitted { get; set; }

		internal bool Permitted { get; private set; }

		internal void RecordPermission(bool permitted)
		{
			PermissionChecked = true;
			Permitted = permitted;
		}
	}
	internal static class SaveAcknowledgementPolicy
	{
		internal static bool CanAcknowledge(ServerProfileSessionState session)
		{
			return session?.CanSave ?? false;
		}
	}
	internal static class CharacterRejectionMessages
	{
		internal const string PermittedListDenied = "This platform account is not allowed to join this server.";

		internal const string AdditionalCharacterDenied = "This platform account already has a character.";
	}
	public interface ICharacterRestoreProvider
	{
		Task<CharacterRestoreResult> RestoreAsync(string platformPlayerId, string playerName, CancellationToken cancellationToken);
	}
	public sealed class CharacterRestoreResult
	{
		public CharacterRestoreStatus Status { get; }

		public byte[] Profile { get; }

		private CharacterRestoreResult(CharacterRestoreStatus status, byte[] profile)
		{
			Status = status;
			Profile = profile;
		}

		public static CharacterRestoreResult Restored(byte[] profile)
		{
			return new CharacterRestoreResult(CharacterRestoreStatus.Restored, profile ?? throw new ArgumentNullException("profile"));
		}

		public static CharacterRestoreResult NotFound()
		{
			return new CharacterRestoreResult(CharacterRestoreStatus.NotFound, null);
		}

		public static CharacterRestoreResult Failed()
		{
			return new CharacterRestoreResult(CharacterRestoreStatus.Failed, null);
		}
	}
	public enum CharacterRestoreStatus
	{
		Restored,
		NotFound,
		Failed
	}
	public static class CharacterRestoreApi
	{
		private static readonly object Sync = new object();

		private static ICharacterRestoreProvider _provider;

		public static void Register(ICharacterRestoreProvider provider)
		{
			if (provider == null)
			{
				throw new ArgumentNullException("provider");
			}
			lock (Sync)
			{
				if (_provider != null && _provider != provider)
				{
					throw new InvalidOperationException("A character restore provider is already registered.");
				}
				_provider = provider;
			}
		}

		public static bool Unregister(ICharacterRestoreProvider provider)
		{
			lock (Sync)
			{
				if (_provider != provider)
				{
					return false;
				}
				_provider = null;
				return true;
			}
		}

		internal static ICharacterRestoreProvider GetProvider()
		{
			lock (Sync)
			{
				return _provider;
			}
		}
	}
	internal sealed class CharacterSaveStatusDisplay
	{
		private const float ActiveDisplaySeconds = 30f;

		private const float CommitTimeoutSeconds = 20f;

		private const float ResultDisplaySeconds = 3f;

		private readonly SaveStatusLifecycle _lifecycle = new SaveStatusLifecycle();

		private TextMeshProUGUI _label;

		internal void Attach(Minimap minimap)
		{
			//IL_00af: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ee: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010c: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0132: 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_01bd: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)_label != (Object)null) && !((Object)(object)minimap?.m_mapImageSmall == (Object)null) && !((Object)(object)minimap.m_biomeNameSmall == (Object)null))
			{
				GameObject val = Object.Instantiate<GameObject>(((Component)minimap.m_biomeNameSmall).gameObject, minimap.m_smallRoot.transform, false);
				((Object)val).name = "CharacterVaultSaveStatus";
				RectTransform component = val.GetComponent<RectTransform>();
				_label = val.GetComponent<TextMeshProUGUI>();
				if ((Object)(object)component == (Object)null || (Object)(object)_label == (Object)null || (Object)(object)((TMP_Text)_label).font == (Object)null)
				{
					Object.Destroy((Object)(object)val);
					_label = null;
					return;
				}
				component.anchorMin = new Vector2(0.5f, 0.5f);
				component.anchorMax = new Vector2(0.5f, 0.5f);
				component.pivot = new Vector2(0.5f, 1f);
				component.sizeDelta = new Vector2(480f, 30f);
				RectTransform rectTransform = ((Graphic)minimap.m_mapImageSmall).rectTransform;
				Rect rect = rectTransform.rect;
				float x = ((Rect)(ref rect)).center.x;
				rect = rectTransform.rect;
				((Transform)component).position = ((Transform)rectTransform).TransformPoint(new Vector3(x, ((Rect)(ref rect)).yMin - 8f, 0f));
				((Transform)component).SetAsLastSibling();
				((TMP_Text)_label).fontSize = minimap.m_biomeNameSmall.fontSize;
				((TMP_Text)_label).fontSizeMax = minimap.m_biomeNameSmall.fontSize;
				((TMP_Text)_label).fontSizeMin = 8f;
				((TMP_Text)_label).enableAutoSizing = true;
				((TMP_Text)_label).textWrappingMode = (TextWrappingModes)0;
				((TMP_Text)_label).fontStyle = (FontStyles)1;
				((TMP_Text)_label).alignment = (TextAlignmentOptions)514;
				((Graphic)_label).color = Color.white;
				((Graphic)_label).raycastTarget = false;
				((TMP_Text)_label).text = string.Empty;
				val.SetActive(false);
			}
		}

		internal void ShowSaving(string requestId)
		{
			Show(requestId, "Saving character...", 30f, waitingForCommit: false);
		}

		internal void ShowAccepted(string requestId)
		{
			int version = Show(requestId, "Saving character......", 30f, waitingForCommit: true);
			CharacterVaultPlugin.Instance?.Run(FailWithoutCommit(requestId, version));
		}

		internal void ShowCommitted(string requestId)
		{
			if (_lifecycle.CanCommit(requestId))
			{
				Show(requestId, "Character saved", 3f, waitingForCommit: false);
			}
		}

		internal void Hide()
		{
			_lifecycle.Clear();
			if ((Object)(object)_label != (Object)null)
			{
				((Component)_label).gameObject.SetActive(false);
			}
		}

		internal void Dispose()
		{
			_lifecycle.Clear();
			if ((Object)(object)_label != (Object)null)
			{
				Object.Destroy((Object)(object)((Component)_label).gameObject);
				_label = null;
			}
		}

		private int Show(string requestId, string message, float duration, bool waitingForCommit)
		{
			int num = _lifecycle.Begin(requestId, waitingForCommit);
			Attach(Minimap.instance);
			if ((Object)(object)_label == (Object)null)
			{
				return num;
			}
			((TMP_Text)_label).text = message;
			((Component)_label).gameObject.SetActive(true);
			CharacterVaultPlugin.Instance?.Run(HideAfterDelay(num, duration));
			return num;
		}

		private IEnumerator FailWithoutCommit(string requestId, int version)
		{
			yield return (object)new WaitForSecondsRealtime(20f);
			if (_lifecycle.CanFail(requestId, version))
			{
				Show(requestId, "Failed", 3f, waitingForCommit: false);
			}
		}

		private IEnumerator HideAfterDelay(int version, float duration)
		{
			yield return (object)new WaitForSecondsRealtime(duration);
			if (_lifecycle.IsCurrent(version))
			{
				Hide();
			}
		}
	}
	internal static class CharacterVaultArgumentPolicy
	{
		private const string MultipleArgument = "--charactervault-allow-multiple-characters";

		private const string ItemsArgument = "--charactervault-starting-items";

		internal static bool TryResolveAllowMultiple(string[] arguments, out bool result)
		{
			result = false;
			if (!TryReadValue(arguments, "--charactervault-allow-multiple-characters", out var value))
			{
				return false;
			}
			if (!bool.TryParse(value, out var result2))
			{
				throw new InvalidOperationException("Command-line switch --charactervault-allow-multiple-characters requires true or false.");
			}
			result = result2;
			return true;
		}

		internal static bool TryResolveStartingItems(string[] arguments, out string result)
		{
			return TryReadValue(arguments, "--charactervault-starting-items", out result);
		}

		private static bool TryReadValue(string[] arguments, string name, out string value)
		{
			value = "";
			bool flag = false;
			for (int i = 0; i < arguments.Length; i++)
			{
				if (string.Equals(arguments[i], name, StringComparison.OrdinalIgnoreCase))
				{
					if (flag || i + 1 >= arguments.Length)
					{
						throw new InvalidOperationException("Command-line switch " + name + " is missing or duplicated.");
					}
					value = arguments[++i];
					flag = true;
				}
			}
			return flag;
		}
	}
	[BepInPlugin("chazman.RunicCharacterVault", "Runic Character Vault", "1.0.2")]
	public sealed class CharacterVaultPlugin : RunicPluginBase
	{
		internal const string PluginGuid = "chazman.RunicCharacterVault";

		internal const string PluginName = "Runic Character Vault";

		internal const string PluginVersion = "1.0.2";

		internal static ModLog Log { get; private set; }

		internal static GracefulShutdownCoordinator Coordinator { get; private set; }

		internal static VoluntaryDisconnectCoordinator DisconnectCoordinator { get; private set; }

		internal static ServerDisconnectSaveCoordinator ServerDisconnects { get; private set; }

		internal static CharacterSaveStatusDisplay SaveStatus { get; private set; }

		internal static CharacterVaultPlugin Instance { get; private set; }

		internal static CharacterVaultSettings Settings { get; private set; }

		internal static ProfileTransferService Transfers { get; private set; }

		internal static bool PlayFabVerboseLogging { get; private set; }

		private void Awake()
		{
			Instance = this;
			PlayFabVerboseLogging = ((BaseUnityPlugin)this).Config.Bind<bool>("Diagnostics", "PlayFabVerboseLogging", false, "Enables verbose PlayFab Party logging for local diagnostics.").Value;
			Log = InitializePlugin("chazman.RunicCharacterVault");
			Settings = new CharacterVaultSettings(((BaseUnityPlugin)this).Config);
			Transfers = new ProfileTransferService(SynchronizationContext.Current);
			Coordinator = new GracefulShutdownCoordinator(SynchronizationContext.Current);
			DisconnectCoordinator = new VoluntaryDisconnectCoordinator();
			ServerDisconnects = new ServerDisconnectSaveCoordinator();
			SaveStatus = new CharacterSaveStatusDisplay();
			PlayFabVerboseDiagnostics.Enable();
			CharacterVaultLobbyLeftDiagnostics.Register();
			Log.LogInfo("Runic Character Vault 1.0.2 is loaded.");
		}

		internal void Run(IEnumerator routine)
		{
			((MonoBehaviour)this).StartCoroutine(routine);
		}

		internal void QuitNextFrame()
		{
			((MonoBehaviour)this).StartCoroutine(QuitAfterCurrentFrame());
		}

		private void Update()
		{
			CharacterVaultRejection.Tick();
			Transfers.MonitorFinalSaves();
		}

		private static IEnumerator QuitAfterCurrentFrame()
		{
			yield return null;
			Application.Quit();
		}

		private void OnDestroy()
		{
			CharacterVaultLobbyLeftDiagnostics.Unregister();
			DisconnectCoordinator?.Dispose();
			ServerDisconnects?.Dispose();
			Coordinator?.Dispose();
			Transfers?.Dispose();
			SaveStatus?.Dispose();
			CharacterVaultRejection.Clear();
			DisconnectCoordinator = null;
			ServerDisconnects = null;
			Coordinator = null;
			Transfers = null;
			SaveStatus = null;
			Settings = null;
			PlayFabVerboseLogging = false;
			Instance = null;
			Log?.LogInfo("Runic Character Vault 1.0.2 is unloaded.");
			ShutdownPlugin();
			Log = null;
		}
	}
	internal static class CharacterVaultRejection
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static Method <0>__ReceiveAck;

			public static Action<ZRpc, ZPackage> <1>__ReceiveMessage;
		}

		internal const string MessageRpc = "RunicCharacterVault_Rejection_v1";

		internal const string AckRpc = "RunicCharacterVault_RejectionAck_v1";

		private const float DisconnectFallbackSeconds = 2f;

		private static readonly Dictionary<ZRpc, float> Deadlines = new Dictionary<ZRpc, float>();

		private static readonly HashSet<ZRpc> DisconnectRequested = new HashSet<ZRpc>();

		private static readonly HashSet<string> PermittedListRejections = new HashSet<string>();

		internal static void RegisterServer(ZRpc rpc)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Expected O, but got Unknown
			object obj = <>O.<0>__ReceiveAck;
			if (obj == null)
			{
				Method val = ReceiveAck;
				<>O.<0>__ReceiveAck = val;
				obj = (object)val;
			}
			rpc.Register("RunicCharacterVault_RejectionAck_v1", (Method)obj);
		}

		internal static void RegisterClient(ZRpc rpc)
		{
			ClearClient();
			rpc.Register<ZPackage>("RunicCharacterVault_Rejection_v1", (Action<ZRpc, ZPackage>)ReceiveMessage);
		}

		internal static void Reject(ZRpc rpc, string message, string systemMessage = null)
		{
			//IL_003b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Expected O, but got Unknown
			CharacterVaultPlugin.Log.LogWarning("CharacterVault rejected " + rpc.GetSocket().GetHostName() + ": " + message);
			Deadlines[rpc] = Time.unscaledTime + 2f;
			ZPackage val = new ZPackage();
			val.Write(message);
			val.Write(systemMessage ?? message);
			rpc.Invoke("RunicCharacterVault_Rejection_v1", new object[1] { val });
		}

		internal static void RecordPermittedListRejection(string hostName)
		{
			PermittedListRejections.Add(hostName);
		}

		internal static void SendPermittedListRejection(ZRpc rpc)
		{
			object obj;
			if (rpc == null)
			{
				obj = null;
			}
			else
			{
				ISocket socket = rpc.GetSocket();
				obj = ((socket != null) ? socket.GetHostName() : null);
			}
			string text = (string)obj;
			if (text != null && PermittedListRejections.Remove(text))
			{
				Reject(rpc, "This platform account is not allowed to join this server.");
			}
		}

		internal static void SetClientMessage(string userMessage, string systemMessage = null)
		{
			ConnectionFailureMessages.Push("RunicCharacterVault", userMessage, systemMessage);
		}

		internal static void ClearPendingClientMessage()
		{
			ClearClient();
		}

		internal static void Remove(ZRpc rpc)
		{
			Deadlines.Remove(rpc);
			DisconnectRequested.Remove(rpc);
		}

		internal static void Tick()
		{
			DisconnectExpired();
		}

		internal static void Clear()
		{
			Deadlines.Clear();
			DisconnectRequested.Clear();
			PermittedListRejections.Clear();
			ClearClient();
		}

		private static void ReceiveMessage(ZRpc rpc, ZPackage package)
		{
			string text = package.ReadString();
			string text2 = package.ReadString();
			ConnectionFailureMessages.Push("RunicCharacterVault", text, text2);
			CharacterVaultPlugin.Log.LogWarning("Server rejected the character: " + text + " System detail: " + text2);
			rpc.Invoke("RunicCharacterVault_RejectionAck_v1", Array.Empty<object>());
			CharacterVaultPlugin.Log.LogDebug("Acknowledged the CharacterVault rejection; waiting for the server disconnect.");
		}

		private static void ReceiveAck(ZRpc rpc)
		{
			if (Deadlines.ContainsKey(rpc))
			{
				RequestDisconnect(rpc);
			}
		}

		private static void DisconnectExpired()
		{
			ZRpc[] array = (from entry in Deadlines
				where Time.unscaledTime >= entry.Value
				select entry.Key).ToArray();
			foreach (ZRpc val in array)
			{
				if (DisconnectRequested.Contains(val))
				{
					ForceDisconnect(val);
				}
				else
				{
					RequestDisconnect(val);
				}
			}
		}

		private static void RequestDisconnect(ZRpc rpc)
		{
			DisconnectRequested.Add(rpc);
			Deadlines[rpc] = Time.unscaledTime + 2f;
			CharacterVaultPlugin.Log.LogDebug("Requesting rejected pre-spawn client disconnection.");
			rpc.Invoke("Disconnect", Array.Empty<object>());
		}

		private static void ForceDisconnect(ZRpc rpc)
		{
			Deadlines.Remove(rpc);
			DisconnectRequested.Remove(rpc);
			ZNet instance = ZNet.instance;
			ZNetPeer val = ((instance != null) ? ((IEnumerable<ZNetPeer>)instance.GetPeers()).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer candidate) => candidate.m_rpc == rpc)) : null);
			if (val != null)
			{
				CharacterVaultPlugin.Log.LogWarning("Rejected client did not disconnect; closing the server connection.");
				ZNet.instance.Disconnect(val);
			}
		}

		private static void ClearClient()
		{
			ConnectionFailureMessages.Clear("RunicCharacterVault");
		}
	}
	internal sealed class CharacterVaultSettings
	{
		private bool serverInitialized;

		private readonly ConfigEntry<bool> allowMultipleCharacters;

		private readonly ConfigEntry<bool> allowExistingCharacters;

		private readonly ConfigEntry<string> startingItems;

		internal bool AllowMultipleCharacters { get; private set; }

		internal bool AllowExistingCharacters { get; private set; }

		internal IReadOnlyList<StartingItem> StartingItems { get; private set; }

		internal CharacterVaultSettings(ConfigFile config)
		{
			StartingItems = new List<StartingItem>();
			allowExistingCharacters = config.Bind<bool>("Server", "AllowExistingCharacters", true, "Import a previously played character when no vault copy exists. The first upload is trusted; existing vault copies are never replaced by enrollment. Disable for fresh-character-only servers.");
			allowMultipleCharacters = config.Bind<bool>("Server", "AllowMultipleCharacters", false, "Allow one platform account to enroll more than one character name.");
			startingItems = config.Bind<string>("Server", "StartingItems", string.Empty, "Optional comma-separated Valheim prefab:quantity pairs for newly enrolled characters.");
		}

		internal void InitializeServer()
		{
			if (!serverInitialized && ServerRole.IsServer)
			{
				string[] commandLineArgs = Environment.GetCommandLineArgs();
				AllowExistingCharacters = allowExistingCharacters.Value;
				AllowMultipleCharacters = (CharacterVaultArgumentPolicy.TryResolveAllowMultiple(commandLineArgs, out var result) ? result : allowMultipleCharacters.Value);
				string result2;
				string value = (CharacterVaultArgumentPolicy.TryResolveStartingItems(commandLineArgs, out result2) ? result2 : startingItems.Value);
				StartingItems = ParseItems(value);
				serverInitialized = true;
				CharacterVaultPlugin.Log.LogInfo($"Server allowExistingCharacters={AllowExistingCharacters}, allowMultipleCharacters={AllowMultipleCharacters}, " + $"startingItemCount={StartingItems.Count}.");
			}
		}

		private static IReadOnlyList<StartingItem> ParseItems(string value)
		{
			List<StartingItem> list = new List<StartingItem>();
			if (string.IsNullOrWhiteSpace(value))
			{
				return list;
			}
			string[] array = value.Split(',');
			foreach (string text in array)
			{
				string[] array2 = text.Split(':');
				if (array2.Length != 2 || string.IsNullOrWhiteSpace(array2[0]) || !int.TryParse(array2[1].Trim(), out var result) || result <= 0)
				{
					throw new InvalidOperationException("Invalid CharacterVault starting item '" + text + "'.");
				}
				list.Add(new StartingItem(array2[0].Trim(), result));
			}
			return list;
		}
	}
	internal sealed class ClientSaveLifecycle
	{
		private bool _active;

		private bool _enrolling;

		private bool _spawned;

		internal bool IsActive => _active;

		internal bool CanUpload
		{
			get
			{
				if (_active)
				{
					return _spawned;
				}
				return false;
			}
		}

		internal bool IsEnrolling => _enrolling;

		internal bool HasSpawned => _spawned;

		internal void ActivateExisting()
		{
			_active = true;
		}

		internal void BeginEnrollment()
		{
			_active = true;
			_enrolling = true;
		}

		internal bool RecordSpawn(bool isLocalPlayer)
		{
			if (!isLocalPlayer)
			{
				return false;
			}
			_spawned = true;
			bool enrolling = _enrolling;
			_enrolling = false;
			return enrolling;
		}

		internal void Reset()
		{
			_active = false;
			_enrolling = false;
			_spawned = false;
		}
	}
	public static class GracefulShutdownApi
	{
		public static bool TryRequest()
		{
			return CharacterVaultPlugin.Coordinator?.TryRequestShutdown() ?? false;
		}
	}
	internal sealed class GracefulShutdownCoordinator : IDisposable
	{
		private const string ExitFilePath = "character_vault.drp";

		private const int MaximumConcurrentSaves = 4;

		private const int ShutdownTimeoutSeconds = 90;

		private const int ClientDisconnectGraceSeconds = 2;

		private readonly HashSet<ZNetPeer> _pendingPeers = new HashSet<ZNetPeer>();

		private readonly HashSet<ZNetPeer> _requestedPeers = new HashSet<ZNetPeer>();

		private readonly HashSet<ZRpc> _shutdownPeerRpcs = new HashSet<ZRpc>();

		private readonly Queue<ZNetPeer> _queuedPeers = new Queue<ZNetPeer>();

		private readonly FileSystemWatcher _exitFileWatcher;

		private readonly SynchronizationContext _unityContext;

		private Timer _timeoutTimer;

		private Timer _disconnectTimer;

		private int _disposed;

		private int _exitRequestQueued;

		private volatile bool _watcherFailed;

		private bool _exitRequestPending;

		private bool _shutdownCommitted;

		private string _requestId;

		internal GracefulShutdownCoordinator(SynchronizationContext unityContext)
		{
			_unityContext = unityContext ?? throw new ArgumentNullException("unityContext");
			try
			{
				_exitFileWatcher = CreateExitFileWatcher();
				if (File.Exists("character_vault.drp"))
				{
					QueueExitRequest();
				}
			}
			catch (Exception arg)
			{
				CharacterVaultPlugin.Log.LogError(string.Format("Could not watch {0}; shutdown requests cannot be detected: {1}", "character_vault.drp", arg));
			}
		}

		private void TryStartFromExitFile()
		{
			if (!CanCoordinateShutdown())
			{
				_exitRequestPending = true;
				return;
			}
			_exitRequestPending = false;
			try
			{
				if (File.Exists("character_vault.drp"))
				{
					string text = File.ReadAllText("character_vault.drp").Trim();
					if (!int.TryParse(text, out var result) || result != Process.GetCurrentProcess().Id)
					{
						File.Delete("character_vault.drp");
						CharacterVaultPlugin.Log.LogWarning("Ignored a stale or invalid character_vault.drp request for process '" + text + "'.");
					}
					else
					{
						File.Delete("character_vault.drp");
						Start(ZNet.instance);
					}
				}
			}
			catch (Exception arg)
			{
				CharacterVaultPlugin.Log.LogError(string.Format("Could not process {0}: {1}", "character_vault.drp", arg));
			}
		}

		internal void RecordSaveCommitted(ZRpc peerRpc, string committedRequestId)
		{
			if (_requestId == null || committedRequestId != _requestId)
			{
				return;
			}
			ZNetPeer val = ((IEnumerable<ZNetPeer>)_pendingPeers).FirstOrDefault((Func<ZNetPeer, bool>)((ZNetPeer candidate) => candidate.m_rpc == peerRpc));
			if (val != null && _pendingPeers.Remove(val))
			{
				_requestedPeers.Remove(val);
				CharacterVaultPlugin.Log.LogMessage($"Confirmed graceful character save for {val.m_playerName} ({_pendingPeers.Count} remaining).");
				RequestNextProfiles();
				if (_pendingPeers.Count == 0)
				{
					Complete();
				}
			}
		}

		private static bool CanCoordinateShutdown()
		{
			if (Application.isBatchMode)
			{
				ZNet instance = ZNet.instance;
				if (instance == null)
				{
					return false;
				}
				return instance.IsServer();
			}
			return false;
		}

		private void Start(ZNet network)
		{
			_requestId = Guid.NewGuid().ToString("N");
			_timeoutTimer = new Timer(OnTimeoutElapsed, _requestId, TimeSpan.FromSeconds(90.0), Timeout.InfiniteTimeSpan);
			foreach (ZNetPeer item in network.GetPeers().Where(HasActiveCharacter))
			{
				_pendingPeers.Add(item);
				_shutdownPeerRpcs.Add(item.m_rpc);
				_queuedPeers.Enqueue(item);
			}
			CharacterVaultPlugin.Log.LogMessage($"Shutdown requested; saving {_pendingPeers.Count} connected character(s).");
			RequestNextProfiles();
			CompleteIfFinished();
		}

		internal void ProcessPendingExitRequest()
		{
			if (_exitRequestPending)
			{
				ProcessExitRequest();
			}
		}

		internal bool TryRequestShutdown()
		{
			if (_shutdownCommitted || _requestId != null || !CanCoordinateShutdown())
			{
				return false;
			}
			Start(ZNet.instance);
			return true;
		}

		private void RequestNextProfiles()
		{
			while (_requestedPeers.Count < 4 && _queuedPeers.Count > 0)
			{
				ZNetPeer val = _queuedPeers.Dequeue();
				if (!_pendingPeers.Contains(val) || !HasActiveCharacter(val))
				{
					_pendingPeers.Remove(val);
					continue;
				}
				_requestedPeers.Add(val);
				CharacterVaultPlugin.Transfers.RequestSave(val, _requestId);
			}
		}

		public void Dispose()
		{
			Interlocked.Exchange(ref _disposed, 1);
			_exitFileWatcher?.Dispose();
			_timeoutTimer?.Dispose();
			_disconnectTimer?.Dispose();
		}

		private void ProcessExitRequest()
		{
			Interlocked.Exchange(ref _exitRequestQueued, 0);
			if (Volatile.Read(in _disposed) == 0 && !_shutdownCommitted)
			{
				if (_watcherFailed)
				{
					_watcherFailed = false;
					CharacterVaultPlugin.Log.LogError("The character_vault.drp watcher failed; shutdown requests may no longer be detected.");
				}
				if (_requestId == null)
				{
					TryStartFromExitFile();
				}
			}
		}

		private FileSystemWatcher CreateExitFileWatcher()
		{
			FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(Path.GetFullPath("."), "character_vault.drp");
			fileSystemWatcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.LastWrite;
			fileSystemWatcher.Created += OnExitFileChanged;
			fileSystemWatcher.Changed += OnExitFileChanged;
			fileSystemWatcher.Renamed += OnExitFileRenamed;
			fileSystemWatcher.Error += OnWatcherError;
			fileSystemWatcher.EnableRaisingEvents = true;
			return fileSystemWatcher;
		}

		private void OnExitFileChanged(object sender, FileSystemEventArgs args)
		{
			QueueExitRequest();
		}

		private void OnExitFileRenamed(object sender, RenamedEventArgs args)
		{
			QueueExitRequest();
		}

		private void OnWatcherError(object sender, ErrorEventArgs args)
		{
			_watcherFailed = true;
			QueueExitRequest();
		}

		private void QueueExitRequest()
		{
			if (Volatile.Read(in _disposed) == 0 && Interlocked.Exchange(ref _exitRequestQueued, 1) == 0)
			{
				_unityContext.Post(delegate
				{
					ProcessExitRequest();
				}, null);
			}
		}

		private void OnTimeoutElapsed(object state)
		{
			string timedOutRequestId = (string)state;
			if (Volatile.Read(in _disposed) == 0)
			{
				_unityContext.Post(delegate
				{
					CompleteAfterTimeoutIfCurrent(timedOutRequestId);
				}, null);
			}
		}

		private void CompleteAfterTimeoutIfCurrent(string timedOutRequestId)
		{
			if (!_shutdownCommitted && timedOutRequestId == _requestId)
			{
				CompleteAfterTimeout();
			}
		}

		private void CompleteIfFinished()
		{
			if (_pendingPeers.Count == 0 && !_shutdownCommitted)
			{
				Complete();
			}
		}

		private void Complete()
		{
			CharacterVaultPlugin.Log.LogMessage("All connected character profiles were written to disk; requesting normal client disconnection.");
			RequestClientDisconnects();
		}

		private void CompleteAfterTimeout()
		{
			string arg = string.Join(", ", _pendingPeers.Select((ZNetPeer peer) => peer.m_playerName).ToArray());
			CharacterVaultPlugin.Log.LogWarning($"The {90}-second shutdown save timeout expired with " + $"{_pendingPeers.Count} unsaved character(s): {arg}.");
			CharacterVaultPlugin.Log.LogWarning("Requesting normal client disconnection after the character save timeout.");
			RequestClientDisconnects();
		}

		private void RequestClientDisconnects()
		{
			ZNetPeer[] array = ConnectedShutdownPeers();
			ZNetPeer[] array2 = array;
			for (int i = 0; i < array2.Length; i++)
			{
				array2[i].m_rpc.Invoke("Disconnect", Array.Empty<object>());
			}
			if (array.Length == 0)
			{
				ContinueVanillaShutdown();
				return;
			}
			CharacterVaultPlugin.Log.LogMessage($"Requested normal disconnection for {array.Length} client(s); " + $"waiting {2} seconds before the server fallback.");
			_disconnectTimer = new Timer(OnDisconnectGraceElapsed, null, TimeSpan.FromSeconds(2.0), Timeout.InfiniteTimeSpan);
		}

		private ZNetPeer[] ConnectedShutdownPeers()
		{
			ZNet instance = ZNet.instance;
			IEnumerable<ZNetPeer> enumerable = ((instance != null) ? instance.GetPeers() : null);
			return (enumerable ?? Enumerable.Empty<ZNetPeer>()).Where((ZNetPeer peer) => peer?.m_rpc != null && _shutdownPeerRpcs.Contains(peer.m_rpc)).ToArray();
		}

		private void OnDisconnectGraceElapsed(object state)
		{
			if (Volatile.Read(in _disposed) == 0)
			{
				_unityContext.Post(delegate
				{
					CompleteClientDisconnects();
				}, null);
			}
		}

		private void CompleteClientDisconnects()
		{
			ZNetPeer[] array = ConnectedShutdownPeers();
			ZNetPeer[] array2 = array;
			foreach (ZNetPeer val in array2)
			{
				ZNet.instance.Disconnect(val);
			}
			CharacterVaultPlugin.Log.LogMessage((array.Length == 0) ? "All clients disconnected normally; continuing the vanilla shutdown." : $"Closed {array.Length} remaining client connection(s); continuing the vanilla shutdown.");
			ContinueVanillaShutdown();
		}

		private void ContinueVanillaShutdown()
		{
			_timeoutTimer?.Dispose();
			_timeoutTimer = null;
			_disconnectTimer?.Dispose();
			_disconnectTimer = null;
			_pendingPeers.Clear();
			_requestedPeers.Clear();
			_shutdownPeerRpcs.Clear();
			_queuedPeers.Clear();
			_requestId = null;
			_shutdownCommitted = true;
			CharacterVaultPlugin.Log.LogMessage("Starting the vanilla application shutdown.");
			CharacterVaultPlugin.Instance.QuitNextFrame();
		}

		private static bool HasActiveCharacter(ZNetPeer peer)
		{
			if (peer?.m_rpc != null)
			{
				ISocket socket = peer.m_socket;
				if (socket != null && socket.IsConnected())
				{
					return !string.IsNullOrWhiteSpace(peer.m_playerName);
				}
			}
			return false;
		}
	}
	internal interface IKickSaveRequest
	{
		KickSaveRequestResult Request();
	}
	internal sealed class KickSaveRequestResult
	{
		internal string RequestId { get; }

		internal bool Started { get; }

		internal KickSaveRequestResult(bool started, string requestId = "")
		{
			Started = started;
			RequestId = requestId;
		}
	}
	internal enum KickSaveEligibility
	{
		Unmanaged,
		Rejected,
		SaveRequired
	}
	internal enum KickAction
	{
		Allow,
		AllowWithoutSave,
		WaitForPendingSave,
		RequestSave
	}
	internal static class KickSavePolicy
	{
		internal static KickAction Decide(bool validServerPeer, bool saveAuthorized, bool savePending, KickSaveEligibility eligibility)
		{
			if (!validServerPeer || saveAuthorized)
			{
				return KickAction.Allow;
			}
			if (eligibility == KickSaveEligibility.Rejected)
			{
				return KickAction.AllowWithoutSave;
			}
			if (savePending)
			{
				return KickAction.WaitForPendingSave;
			}
			if (eligibility != KickSaveEligibility.SaveRequired)
			{
				return KickAction.AllowWithoutSave;
			}
			return KickAction.RequestSave;
		}
	}
	internal static class KickSaveRequestExecutor
	{
		internal static KickSaveRequestResult Execute(KickAction action, IKickSaveRequest request)
		{
			if (action != KickAction.RequestSave)
			{
				return new KickSaveRequestResult(started: false);
			}
			return request.Request();
		}
	}
	internal static class NewCharacterPolicy
	{
		internal static bool HasNeverJoinedAWorld(PlayerProfile profile)
		{
			if (profile == null || !profile.m_firstSpawn || profile.m_playerStats == null)
			{
				return false;
			}
			PlayerStats[] playerStats = profile.m_playerStats;
			foreach (PlayerStats val in playerStats)
			{
				if (val != null && val.m_knownWorlds != null && val.m_knownWorlds.Count > 0)
				{
					return false;
				}
			}
			return true;
		}
	}
	internal static class PlayFabAckDiagnostics
	{
		private const int HistoryLimit = 32;

		private static readonly ConditionalWeakTable<InFlightQueue, Queue<string>> Histories = new ConditionalWeakTable<InFlightQueue, Queue<string>>();

		internal static void RecordEnqueue(InFlightQueue queue, byte[] payload)
		{
			Record(queue, "enqueue " + DescribePayload(payload) + "; " + DescribeQueue(queue));
		}

		internal static void RecordDrop(InFlightQueue queue, byte[] payload)
		{
			Record(queue, "drop " + DescribePayload(payload) + "; " + DescribeQueue(queue));
		}

		internal static void RecordReset(InFlightQueue queue)
		{
			Record(queue, "reset; " + DescribeQueue(queue));
		}

		internal static void AckReceived(ZPlayFabSocket socket, uint messageId, InFlightQueue queue, bool isClient)
		{
			uint num = queue.Head - queue.Tail;
			uint num2 = messageId - queue.Tail;
			if (num2 > num)
			{
				CharacterVaultPlugin.Log.LogError($"Invalid PlayFab ACK received: ack={messageId}, acknowledged={num2}, " + $"outstanding={num}, {DescribeQueue(queue)}, " + "socket=" + DescribeSocket(socket, isClient) + ". Queue history:\n" + GetHistory(queue) + "\nCall stack:\n" + Environment.StackTrace);
			}
		}

		internal static void RecordIncomingBuffer(ZPlayFabSocket socket, byte[] buffer, bool isClient, bool useCompression, string stage)
		{
			if (buffer != null && buffer.Length > 5 && buffer[^1] == 42)
			{
				CharacterVaultPlugin.Log.LogWarning("PlayFab ACK-shaped buffer at " + stage + ": " + DescribePayload(buffer) + ", " + $"compression={useCompression}, socket={DescribeSocket(socket, isClient)}, " + "first=" + DescribeBytes(buffer, 0) + ", last=" + DescribeBytes(buffer, Math.Max(0, buffer.Length - 32)) + ".");
			}
		}

		internal static bool IsEarlyCompressedBuffer(byte[] buffer)
		{
			if (buffer == null || buffer.Length <= 5)
			{
				return false;
			}
			int num = (buffer[0] << 8) | buffer[1];
			if ((buffer[0] & 0xF) == 8)
			{
				return num % 31 == 0;
			}
			return false;
		}

		internal static void EarlyCompressedBufferQueued(ZPlayFabSocket socket, byte[] buffer, bool isClient)
		{
			CharacterVaultPlugin.Log.LogWarning("PlayFab compressed buffer arrived before VersionMatch; queued for selective " + $"decompression: bytes={buffer.Length}, socket={DescribeSocket(socket, isClient)}, " + "first=" + DescribeBytes(buffer, 0) + ", last=" + DescribeBytes(buffer, Math.Max(0, buffer.Length - 32)) + ".");
		}

		internal static void AckProcessed(ZPlayFabSocket socket, uint messageId, InFlightQueue queue, bool isClient)
		{
			if (!socket.IsConnected())
			{
				CharacterVaultPlugin.Log.LogError($"PlayFab ACK closed the socket: ack={messageId}, {DescribeQueue(queue)}, " + "socket=" + DescribeSocket(socket, isClient) + ". Queue history:\n" + GetHistory(queue));
			}
		}

		private static void Record(InFlightQueue queue, string entry)
		{
			Queue<string> orCreateValue = Histories.GetOrCreateValue(queue);
			orCreateValue.Enqueue($"{DateTime.UtcNow:O} {entry}");
			while (orCreateValue.Count > 32)
			{
				orCreateValue.Dequeue();
			}
		}

		private static string GetHistory(InFlightQueue queue)
		{
			if (!Histories.TryGetValue(queue, out var value))
			{
				return "<empty>";
			}
			return string.Join("\n", value.ToArray());
		}

		private static string DescribeQueue(InFlightQueue queue)
		{
			return $"queue={RuntimeHelpers.GetHashCode(queue):X8}, head={queue.Head}, " + $"tail={queue.Tail}, bytes={queue.Bytes}, empty={queue.IsEmpty}";
		}

		private static string DescribeSocket(ZPlayFabSocket socket, bool isClient)
		{
			return $"object={RuntimeHelpers.GetHashCode(socket):X8}, " + string.Format("side={0}, connected={1}, ", isClient ? "client" : "server", socket.IsConnected()) + "remote=" + PlayFabConnectionDiagnostics.Fingerprint(socket.m_remotePlayerId);
		}

		private static string DescribePayload(byte[] payload)
		{
			if (payload == null || payload.Length < 5)
			{
				return $"bytes={((payload != null) ? payload.Length : 0)}";
			}
			int num = payload.Length - 5;
			uint num2 = (uint)(payload[num] | (payload[num + 1] << 8) | (payload[num + 2] << 16) | (payload[num + 3] << 24));
			return $"id={num2}, type={payload[^1]}, bytes={payload.Length}";
		}

		private static string DescribeBytes(byte[] payload, int offset)
		{
			int num = Math.Min(32, payload.Length - offset);
			if (num <= 0)
			{
				return "<empty>";
			}
			return BitConverter.ToString(payload, offset, num);
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "OnDataMessageReceived")]
	internal static class CharacterVaultPlayFabRawReceiveDiagnosticsPatch
	{
		private static bool Prefix(ZPlayFabSocket __instance, PlayFabPlayer from, byte[] compressedBuffer, bool ___m_isClient, bool ___m_useCompression, PlayFabZLibWorkQueue ___m_zlibWorkQueue)
		{
			PlayFabAckDiagnostics.RecordIncomingBuffer(__instance, compressedBuffer, ___m_isClient, ___m_useCompression, "raw receive");
			if (___m_useCompression || ((from == null) ? null : from.EntityKey?.Id) != __instance.m_remotePlayerId || !PlayFabAckDiagnostics.IsEarlyCompressedBuffer(compressedBuffer))
			{
				return true;
			}
			PlayFabAckDiagnostics.EarlyCompressedBufferQueued(__instance, compressedBuffer, ___m_isClient);
			___m_zlibWorkQueue.Decompress(compressedBuffer);
			return false;
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "OnDataMessageReceivedCont")]
	internal static class CharacterVaultPlayFabDecodedReceiveDiagnosticsPatch
	{
		private static void Prefix(ZPlayFabSocket __instance, byte[] buffer, bool ___m_isClient, bool ___m_useCompression)
		{
			PlayFabAckDiagnostics.RecordIncomingBuffer(__instance, buffer, ___m_isClient, ___m_useCompression, "decoded receive");
		}
	}
	[HarmonyPatch(typeof(InFlightQueue), "Enqueue")]
	internal static class CharacterVaultPlayFabQueueEnqueueDiagnosticsPatch
	{
		private static void Postfix(InFlightQueue __instance, byte[] payload)
		{
			PlayFabAckDiagnostics.RecordEnqueue(__instance, payload);
		}
	}
	[HarmonyPatch(typeof(InFlightQueue), "Drop")]
	internal static class CharacterVaultPlayFabQueueDropDiagnosticsPatch
	{
		private static void Prefix(InFlightQueue __instance, out byte[] __state)
		{
			__state = (__instance.IsEmpty ? null : __instance.Peek());
		}

		private static void Postfix(InFlightQueue __instance, byte[] __state)
		{
			PlayFabAckDiagnostics.RecordDrop(__instance, __state);
		}
	}
	[HarmonyPatch(typeof(InFlightQueue), "ResetAll")]
	internal static class CharacterVaultPlayFabQueueResetDiagnosticsPatch
	{
		private static void Postfix(InFlightQueue __instance)
		{
			PlayFabAckDiagnostics.RecordReset(__instance);
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "ProcessAck")]
	internal static class CharacterVaultPlayFabProcessAckDiagnosticsPatch
	{
		private static void Prefix(ZPlayFabSocket __instance, uint msgId, InFlightQueue ___m_inFlightQueue, bool ___m_isClient)
		{
			PlayFabAckDiagnostics.AckReceived(__instance, msgId, ___m_inFlightQueue, ___m_isClient);
		}

		private static void Postfix(ZPlayFabSocket __instance, uint msgId, InFlightQueue ___m_inFlightQueue, bool ___m_isClient)
		{
			PlayFabAckDiagnostics.AckProcessed(__instance, msgId, ___m_inFlightQueue, ___m_isClient);
		}
	}
	internal static class PlayFabVerboseDiagnostics
	{
		private static bool logged;

		internal static void Enable()
		{
			PlayFabMultiplayerManager val = PlayFabMultiplayerManager.Get();
			if ((Object)(object)val == (Object)null)
			{
				return;
			}
			CharacterVaultLeaveNetworkDiagnosticsPatch.Observe(val);
			PlayFabEndpointDiagnostics.Observe(val);
			if (CharacterVaultPlugin.PlayFabVerboseLogging)
			{
				val.LogLevel = (LogLevelType)2;
				if (!logged)
				{
					logged = true;
					CharacterVaultPlugin.Log?.LogDebug("Verbose PlayFab Party logging is enabled.");
				}
			}
		}
	}
	internal static class PlayFabConnectionDiagnostics
	{
		private sealed class Attempt
		{
			internal int Id { get; }

			internal string Origin { get; }

			internal bool Connected { get; set; }

			internal bool Admitted { get; set; }

			internal bool Failed { get; set; }

			internal List<Tuple<string, string>> Failures { get; } = new List<Tuple<string, string>>();

			internal bool FailuresPublished { get; set; }

			internal Attempt(int id, string origin)
			{
				Id = id;
				Origin = origin;
			}

			internal void AddFailure(string userMessage, string systemMessage)
			{
				Tuple<string, string> item = Tuple.Create(userMessage, systemMessage);
				if (!Failures.Contains(item))
				{
					Failures.Add(item);
				}
			}
		}

		private static readonly ConditionalWeakTable<ZPlayFabSocket, Attempt> Attempts = new ConditionalWeakTable<ZPlayFabSocket, Attempt>();

		private static int nextAttempt;

		private static string selectedOrigin = "unknown";

		private static WeakReference currentClientSocket;

		internal static void Start(ZPlayFabSocket socket)
		{
			CharacterVaultRejection.ClearPendingClientMessage();
			Attempt attempt = new Attempt(Interlocked.Increment(ref nextAttempt), selectedOrigin);
			Attempts.Add(socket, attempt);
			currentClientSocket = new WeakReference(socket);
			selectedOrigin = "unknown";
			CharacterVaultPlugin.Log?.LogDebug($"PlayFab connection attempt {attempt.Id} started; origin={attempt.Origin}.");
		}

		internal static void SelectOrigin(string origin)
		{
			selectedOrigin = origin ?? "unknown";
		}

		internal static void SessionFound(ZPlayFabSocket socket, PlayFabMatchmakingServerData server)
		{
			if (Attempts.TryGetValue(socket, out var value))
			{
				CharacterVaultPlugin.Log?.LogDebug($"PlayFab connection attempt {value.Id} resolved lobby={Fingerprint(server?.lobbyId)}, " + "network=" + Fingerprint(server?.networkId) + ".");
			}
		}

		internal static void NetworkJoined(ZPlayFabSocket socket, string networkId)
		{
			if (Attempts.TryGetValue(socket, out var value))
			{
				CharacterVaultPlugin.Log?.LogDebug($"PlayFab connection attempt {value.Id} joined network={Fingerprint(networkId)}.");
			}
		}

		internal static void Connected(ZPlayFabSocket socket)
		{
			if (Attempts.TryGetValue(socket, out var value) && !value.Connected)
			{
				value.Connected = true;
				CharacterVaultPlugin.Log?.LogDebug($"PlayFab connection attempt {value.Id} established the remote transport.");
			}
		}

		internal static void Admitted()
		{
			object? obj = currentClientSocket?.Target;
			ZPlayFabSocket val = (ZPlayFabSocket)((obj is ZPlayFabSocket) ? obj : null);
			if (val != null && Attempts.TryGetValue(val, out var value) && !value.Admitted)
			{
				value.Admitted = true;
				value.Failures.Clear();
				CharacterVaultRejection.ClearPendingClientMessage();
				CharacterVaultPlugin.Log?.LogDebug($"PlayFab connection attempt {value.Id} completed the Valheim peer handshake.");
			}
		}

		internal unsafe static void Failed(ZPlayFabSocket socket, ZPLayFabMatchmakingFailReason reason)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_0057: Unknown result type (might be due to invalid IL or missing references)
			if (Attempts.TryGetValue(socket, out var value))
			{
				value.Failed = true;
				if (value.Failures.Count == 0)
				{
					value.AddFailure(PlayFabConnectionErrorMessages.ForMatchmaking(reason), ((object)(*(ZPLayFabMatchmakingFailReason*)(&reason))/*cast due to .constrained prefix*/).ToString());
				}
				CharacterVaultPlugin.Log?.LogWarning($"PlayFab connection attempt {value.Id} failed while locating the network: {reason}.");
			}
		}

		internal static void Closed(ZPlayFabSocket socket)
		{
			//IL_0069: Unknown result type (might be due to invalid IL or missing references)
			if (Attempts.TryGetValue(socket, out var value))
			{
				string arg = (value.Failed ? "failed" : (value.Admitted ? "admitted" : (value.Connected ? "transport-only" : "incomplete")));
				CharacterVaultPlugin.Log?.LogDebug($"PlayFab connection attempt {value.Id} socket closed; outcome={arg}, " + $"status={ZNet.GetConnectionStatus()}.");
				if (!value.Admitted)
				{
					PublishFailures(value);
				}
				Attempts.Remove(socket);
				if (currentClientSocket?.Target == socket)
				{
					currentClientSocket = null;
				}
			}
		}

		internal static void LobbyFailure(string stage, PlayFabError error, string lobbyId = null)
		{
			//IL_0004: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Invalid comparison between Unknown and I4
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			if (error != null && (int)error.Error == 13002)
			{
				CharacterVaultPlugin.Log?.LogDebug("PlayFab lobby stage " + stage + " reported an existing membership for lobby=" + Fingerprint(lobbyId) + "; continuing.");
			}
			else
			{
				RecordCurrentFailure(PlayFabConnectionErrorMessages.ForApi(error), DescribeApiError(stage, error));
				CharacterVaultPlugin.Log?.LogWarning("PlayFab lobby stage " + stage + " failed for lobby=" + Fingerprint(lobbyId) + ": " + string.Format("code={0}, http={1}, message={2}.", error?.Error, error?.HttpCode, error?.ErrorMessage ?? "unavailable"));
			}
		}

		internal static void ManagerError(int code, string systemMessage)
		{
			RecordCurrentFailure(PlayFabConnectionErrorMessages.ForParty(code), systemMessage ?? $"PlayFab Party error {code}");
		}

		internal static void PublishBlockingFailure()
		{
			object? obj = currentClientSocket?.Target;
			ZPlayFabSocket val = (ZPlayFabSocket)((obj is ZPlayFabSocket) ? obj : null);
			if (val != null && Attempts.TryGetValue(val, out var value) && !value.Admitted && value.Failures.Count != 0)
			{
				PublishFailures(value);
				CharacterVaultPlugin.Log?.LogWarning($"Published {value.Failures.Count} blocking PlayFab error(s) for " + $"connection attempt {value.Id}.");
			}
		}

		private static void RecordCurrentFailure(string userMessage, string systemMessage)
		{
			object? obj = currentClientSocket?.Target;
			ZPlayFabSocket val = (ZPlayFabSocket)((obj is ZPlayFabSocket) ? obj : null);
			if (val != null && Attempts.TryGetValue(val, out var value) && !value.Admitted)
			{
				value.AddFailure(userMessage, systemMessage);
			}
		}

		private static void PublishFailures(Attempt attempt)
		{
			if (attempt.FailuresPublished)
			{
				return;
			}
			foreach (Tuple<string, string> failure in attempt.Failures)
			{
				CharacterVaultRejection.SetClientMessage(failure.Item1, failure.Item2);
			}
			attempt.FailuresPublished = true;
		}

		private static string DescribeApiError(string stage, PlayFabError error)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			return $"PlayFab {stage} failed: code={error?.Error}, http={error?.HttpCode}, " + "message=" + (error?.ErrorMessage ?? "unavailable");
		}

		internal static string Fingerprint(string value)
		{
			if (string.IsNullOrEmpty(value))
			{
				return "none";
			}
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(value)), 0, 4).Replace("-", string.Empty);
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class PlayFabClientSocketCreatedPatch
	{
		private static void Postfix(ZPlayFabSocket __instance)
		{
			PlayFabVerboseDiagnostics.Enable();
			PlayFabConnectionDiagnostics.Start(__instance);
		}
	}
	[HarmonyPatch(/*Could not decode attribute arguments.*/)]
	internal static class PlayFabServerSocketCreatedPatch
	{
		private static void Postfix()
		{
			PlayFabVerboseDiagnostics.Enable();
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "ClientConnect")]
	internal static class PlayFabClientConnectVerboseLoggingPatch
	{
		private static void Prefix()
		{
			PlayFabVerboseDiagnostics.Enable();
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "OnRemotePlayerSessionFound")]
	internal static class PlayFabSessionFoundPatch
	{
		private static void Prefix(ZPlayFabSocket __instance, PlayFabMatchmakingServerData serverData)
		{
			PlayFabConnectionDiagnostics.SessionFound(__instance, serverData);
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "OnRemotePlayerNotFound")]
	internal static class PlayFabSessionNotFoundPatch
	{
		private static void Prefix(ZPlayFabSocket __instance, ZPLayFabMatchmakingFailReason failReason)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			PlayFabConnectionDiagnostics.Failed(__instance, failReason);
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "OnNetworkJoined")]
	internal static class PlayFabNetworkJoinedPatch
	{
		private static void Prefix(ZPlayFabSocket __instance, string networkId)
		{
			PlayFabConnectionDiagnostics.NetworkJoined(__instance, networkId);
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "Connect")]
	internal static class PlayFabTransportConnectedPatch
	{
		private static void Postfix(ZPlayFabSocket __instance)
		{
			PlayFabConnectionDiagnostics.Connected(__instance);
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "Dispose")]
	internal static class PlayFabSocketDisposedPatch
	{
		private static void Prefix(ZPlayFabSocket __instance)
		{
			PlayFabConnectionDiagnostics.Closed(__instance);
		}
	}
	[HarmonyPatch(typeof(ZPlayFabLobbySearch), "OnJoinLobbyFailed")]
	internal static class PlayFabJoinLobbyFailedPatch
	{
		private static void Prefix(PlayFabError error, string lobbyId)
		{
			PlayFabConnectionDiagnostics.LobbyFailure("JoinLobby", error, lobbyId);
		}
	}
	[HarmonyPatch(typeof(ZPlayFabLobbySearch), "OnGetLobbyFailed")]
	internal static class PlayFabGetLobbyFailedPatch
	{
		private static void Prefix(PlayFabError error)
		{
			PlayFabConnectionDiagnostics.LobbyFailure("GetLobby", error);
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	internal static class PlayFabPeerHandshakeCompletedPatch
	{
		private static void Postfix(bool ___m_isServer)
		{
			//IL_0003: Unknown result type (might be due to invalid IL or missing references)
			//IL_0009: Invalid comparison between Unknown and I4
			if (!___m_isServer && (int)ZNet.GetConnectionStatus() == 2)
			{
				PlayFabConnectionDiagnostics.Admitted();
			}
		}
	}
	[HarmonyPatch(typeof(ServerListGui), "OnSelectedServer")]
	internal static class PlayFabServerListOriginPatch
	{
		private static void Prefix(List<IServerList> ___m_serverLists, int ___m_currentServerList, LocalServerList ___m_favoriteServersList, LocalServerList ___m_recentServersList)
		{
			if (___m_currentServerList >= 0 && ___m_currentServerList < ___m_serverLists.Count)
			{
				IServerList val = ___m_serverLists[___m_currentServerList];
				PlayFabConnectionDiagnostics.SelectOrigin(((object)val == ___m_favoriteServersList) ? "favorite" : (((object)val == ___m_recentServersList) ? "recent" : ((val is FriendsServerList) ? "friends" : ((val is CommunityServerList) ? "community" : "server-list"))));
			}
		}
	}
	[HarmonyPatch(typeof(FejdStartup), "AutoJoinServer")]
	internal static class PlayFabJoinCodeOriginPatch
	{
		private static void Prefix()
		{
			PlayFabConnectionDiagnostics.SelectOrigin("join-code");
		}
	}
	internal static class PlayFabConnectionErrorMessages
	{
		internal static string ForApi(PlayFabError error)
		{
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Invalid comparison between Unknown and I4
			string text = ((object)Unsafe.As<PlayFabErrorCode, PlayFabErrorCode>(ref error?.Error)/*cast due to .constrained prefix*/).ToString() ?? HttpCode(error);
			if (IsRateLimited(error))
			{
				return "Too many attempts. Please wait and try again. (Code 429)";
			}
			if (error != null && (int)error.Error == 13003)
			{
				return "The server is not accepting connections. (Code " + text + ")";
			}
			return "A PlayFab connection error occurred. Please try again. (Code " + text + ")";
		}

		internal static string ForMatchmaking(ZPLayFabMatchmakingFailReason reason)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0002: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected I4, but got Unknown
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			return (reason - 3) switch
			{
				0 => "The server information is invalid. (Code InvalidServerData)", 
				1 => "The server is full. (Code ServerFull)", 
				2 => "PlayFab sign-in failed. Please try again. (Code NotLoggedIn)", 
				3 => "Too many attempts. Please wait and try again. (Code 429)", 
				4 => "The server is not reachable. (Code EndPointNotOnInternet)", 
				5 => "The connection request is invalid. (Code InvalidParameter)", 
				_ => $"A PlayFab connection error occurred. Please try again. (Code {reason})", 
			};
		}

		internal static string ForParty(int code)
		{
			return code switch
			{
				11 => "PlayFab is not ready. Please try again. (Code 11)", 
				4098 => "The PlayFab connection expired. Please try again. (Code 4098)", 
				_ => $"A PlayFab connection error occurred. Please try again. (Code {code})", 
			};
		}

		private static bool IsRateLimited(PlayFabError error)
		{
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_001e: Invalid comparison between Unknown and I4
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Invalid comparison between Unknown and I4
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Invalid comparison between Unknown and I4
			if ((error == null || error.HttpCode != 429) && (error == null || (int)error.Error != 1130) && (error == null || (int)error.Error != 1199))
			{
				if (error == null)
				{
					return false;
				}
				return (int)error.Error == 13008;
			}
			return true;
		}

		private static string HttpCode(PlayFabError error)
		{
			if (error != null && error.HttpCode > 0)
			{
				return error.HttpCode.ToString();
			}
			return "Unknown";
		}
	}
	[HarmonyPatch(typeof(ZNet), "StopAll")]
	internal static class CharacterVaultZNetStopAllDiagnosticsPatch
	{
		private static void Prefix(ZNet __instance, bool suspending)
		{
			CharacterVaultPlugin.Log.LogDebug($"PlayFab teardown: entering ZNet.StopAll(suspending={suspending}), " + $"isServer={__instance.IsServer()}, peers={__instance.GetPeers().Count}.");
		}

		private static void Postfix(ZNet __instance, bool suspending)
		{
			CharacterVaultPlugin.Log.LogDebug($"PlayFab teardown: ZNet.StopAll(suspending={suspending}) returned, " + $"isServer={__instance.IsServer()}, peers={__instance.GetPeers().Count}.");
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "Dispose")]
	internal static class CharacterVaultPlayFabSocketDisposeDiagnosticsPatch
	{
		private static void Prefix(ZPlayFabSocket __instance)
		{
			CharacterVaultPlugin.Log.LogDebug("PlayFab teardown: entering ZPlayFabSocket.Dispose: " + Describe(__instance) + ".");
		}

		private static void Postfix(ZPlayFabSocket __instance)
		{
			CharacterVaultPlugin.Log.LogDebug("PlayFab teardown: ZPlayFabSocket.Dispose returned: " + Describe(__instance) + ".");
		}

		private static string Describe(ZPlayFabSocket socket)
		{
			Traverse val = Traverse.Create((object)socket);
			return string.Format("state={0}, ", val.Field("m_state").GetValue()) + "lobbyId=" + (val.Field<string>("m_lobbyId").Value ?? "<null>") + ", remotePlayerId=" + (socket.m_remotePlayerId ?? "<null>") + ", " + $"connected={socket.IsConnected()}";
		}
	}
	[HarmonyPatch(typeof(ZPlayFabMatchmaking), "LeaveLobby")]
	internal static class CharacterVaultLeaveLobbyDiagnosticsPatch
	{
		private static void Prefix(string lobbyId)
		{
			CharacterVaultPlugin.Log.LogDebug("PlayFab teardown: requesting LeaveLobby for " + (lobbyId ?? "<null>") + ".");
		}
	}
	[HarmonyPatch(typeof(PlayFabMultiplayerManager), "LeaveNetwork")]
	internal static class CharacterVaultLeaveNetworkDiagnosticsPatch
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static OnNetworkLeftHandler <0>__NetworkLeft;

			public static OnErrorEventHandler <1>__NetworkError;
		}

		private static readonly HashSet<PlayFabMultiplayerManager> Observed = new HashSet<PlayFabMultiplayerManager>();

		private static void Prefix(PlayFabMultiplayerManager __instance)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			Observe(__instance);
			CharacterVaultPlugin.Log.LogDebug($"PlayFab teardown: requesting LeaveNetwork: state={__instance.State}, " + "networkId=" + (__instance.NetworkId ?? "<null>") + ".");
		}

		private static void Postfix(PlayFabMultiplayerManager __instance)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			CharacterVaultPlugin.Log.LogDebug($"PlayFab teardown: LeaveNetwork returned: state={__instance.State}, " + "networkId=" + (__instance.NetworkId ?? "<null>") + ".");
		}

		internal static void Observe(PlayFabMultiplayerManager manager)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Expected O, but got Unknown
			if ((Object)(object)manager != (Object)null && Observed.Add(manager))
			{
				object obj = <>O.<0>__NetworkLeft;
				if (obj == null)
				{
					OnNetworkLeftHandler val = NetworkLeft;
					<>O.<0>__NetworkLeft = val;
					obj = (object)val;
				}
				manager.OnNetworkLeft += (OnNetworkLeftHandler)obj;
				object obj2 = <>O.<1>__NetworkError;
				if (obj2 == null)
				{
					OnErrorEventHandler val2 = NetworkError;
					<>O.<1>__NetworkError = val2;
					obj2 = (object)val2;
				}
				manager.OnError += (OnErrorEventHandler)obj2;
			}
		}

		private static void NetworkLeft(object sender, string networkId)
		{
			//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)
			PlayFabMultiplayerManager val = (PlayFabMultiplayerManager)((sender is PlayFabMultiplayerManager) ? sender : null);
			CharacterVaultPlugin.Log.LogDebug("PlayFab teardown: OnNetworkLeft completed for networkId=" + (networkId ?? "<null>") + ", state=" + (((val != null) ? ((object)val.State/*cast due to .constrained prefix*/).ToString() : null) ?? "<unknown>") + ", currentNetworkId=" + (((val != null) ? val.NetworkId : null) ?? "<null>") + ".");
		}

		private static void NetworkError(object sender, PlayFabMultiplayerManagerErrorArgs args)
		{
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Unknown result type (might be due to invalid IL or missing references)
			PlayFabMultiplayerManager val = (PlayFabMultiplayerManager)((sender is PlayFabMultiplayerManager) ? sender : null);
			if (args != null)
			{
				PlayFabConnectionDiagnostics.ManagerError(args.Code, args.Message);
			}
			CharacterVaultPlugin.Log.LogError($"PlayFab manager error: code={((args != null) ? new int?(args.Code) : ((int?)null))}, type={((args != null) ? new PlayFabMultiplayerManagerErrorType?(args.Type) : ((PlayFabMultiplayerManagerErrorType?)null))}, " + "message=" + (((args != null) ? args.Message : null) ?? "<null>") + ", state=" + (((val != null) ? ((object)val.State/*cast due to .constrained prefix*/).ToString() : null) ?? "<unknown>") + ", networkId=" + (((val != null) ? val.NetworkId : null) ?? "<null>") + ".");
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "ScheduleResetParty")]
	internal static class CharacterVaultScheduleResetPartyDiagnosticsPatch
	{
		private static void Prefix()
		{
			CharacterVaultPlugin.Log.LogWarning("PlayFab recovery: ZPlayFabSocket.ScheduleResetParty requested.");
		}

		private static void Postfix()
		{
			float value = Traverse.Create(typeof(ZPlayFabSocket)).Field<float>("s_durationToPartyReset").Value;
			CharacterVaultPlugin.Log.LogWarning($"PlayFab recovery: global ResetParty scheduled in {value:0.000}s.");
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "ResetPartyTimeout")]
	internal static class CharacterVaultResetPartyTimeoutDiagnosticsPatch
	{
		private static void Prefix(ZPlayFabSocket __instance)
		{
			CharacterVaultPlugin.Log.LogWarning("PlayFab recovery: entering socket ResetPartyTimeout. " + Describe(__instance) + ".");
		}

		private static void Postfix(ZPlayFabSocket __instance)
		{
			Traverse val = Traverse.Create((object)__instance);
			CharacterVaultPlugin.Log.LogWarning("PlayFab recovery: socket reset timers set: " + string.Format("reconnect={0:0.000}s, ", val.Field<float>("m_partyResetConnectTimeout").Value) + string.Format("reset={0:0.000}s.", val.Field<float>("m_partyResetTimeout").Value));
		}

		private static string Describe(ZPlayFabSocket socket)
		{
			Traverse val = Traverse.Create((object)socket);
			return string.Format("state={0}, ", val.Field("m_state").GetValue()) + "remotePlayerId=" + (socket.m_remotePlayerId ?? "<null>") + ", " + string.Format("isClient={0}", val.Field<bool>("m_isClient").Value);
		}
	}
	[HarmonyPatch(typeof(ZPlayFabSocket), "CancelResetParty")]
	internal static class CharacterVaultCancelResetPartyDiagnosticsPatch
	{
		private static void Prefix(ZPlayFabSocket __instance)
		{
			Traverse val = Traverse.Create((object)__instance);
			CharacterVaultPlugin.Log.LogDebug("PlayFab recovery: CancelResetParty: remotePlayerId=" + (__instance.m_remotePlayerId ?? "<null>") + ", " + string.Format("resetRemaining={0:0.000}s, ", val.Field<float>("m_partyResetTimeout").Value) + string.Format("reconnectRemaining={0:0.000}s.", val.Field<float>("m_partyResetConnectTimeout").Value));
		}
	}
	[HarmonyPatch(typeof(PlayFabMultiplayerManager), "ResetParty")]
	internal static class CharacterVaultResetPartyDiagnosticsPatch
	{
		private static void Prefix(PlayFabMultiplayerManager __instance)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			CharacterVaultPlugin.Log.LogWarning($"PlayFab recovery: entering ResetParty: state={__instance.State}, " + "networkId=" + (__instance.NetworkId ?? "<null>") + ".");
		}

		private static void Postfix(PlayFabMultiplayerManager __instance)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			object value = Traverse.Create((object)__instance).Field("_tasks").GetValue();
			CharacterVaultPlugin.Log.LogWarning($"PlayFab recovery: ResetParty queued tasks: state={__instance.State}, " + string.Format("networkId={0}, tasks={1}.", __instance.NetworkId ?? "<null>", value));
		}
	}
	[HarmonyPatch]
	internal static class CharacterVaultResetPartyTaskDiagnosticsPatch
	{
		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type manager = typeof(PlayFabMultiplayerManager);
			string[] array = new string[4] { "LeaveNetworkTask", "CleanPartyTask", "InitPartyTask", "JoinPartyTask" };
			string[] array2 = array;
			foreach (string text in array2)
			{
				Type task = AccessTools.Inner(manager, text);
				yield return AccessTools.Method(task, "Begin", (Type[])null, (Type[])null);
				yield return AccessTools.Method(task, "End", (Type[])null, (Type[])null);
			}
		}

		private static void Prefix(object __instance, MethodBase __originalMethod)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			PlayFabMultiplayerManager val = PlayFabMultiplayerManager.Get();
			CharacterVaultPlugin.Log.LogDebug("PlayFab recovery task: " + __instance.GetType().Name + "." + __originalMethod.Name + " entering; " + string.Format("managerState={0}, networkId={1}.", val.State, val.NetworkId ?? "<null>"));
		}

		private static void Postfix(object __instance, MethodBase __originalMethod)
		{
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			PlayFabMultiplayerManager val = PlayFabMultiplayerManager.Get();
			CharacterVaultPlugin.Log.LogDebug("PlayFab recovery task: " + __instance.GetType().Name + "." + __originalMethod.Name + " returned; " + string.Format("managerState={0}, networkId={1}.", val.State, val.NetworkId ?? "<null>"));
		}
	}
	internal static class CharacterVaultLobbyLeftDiagnostics
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static ZPlayFabMatchmakeLobbyLeftCallback <0>__LobbyLeft;
		}

		internal static void Register()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			//IL_003b: Expected O, but got Unknown
			object obj = <>O.<0>__LobbyLeft;
			if (obj == null)
			{
				ZPlayFabMatchmakeLobbyLeftCallback val = LobbyLeft;
				<>O.<0>__LobbyLeft = val;
				obj = (object)val;
			}
			ZPlayFabMatchmaking.LobbyLeft -= (ZPlayFabMatchmakeLobbyLeftCallback)obj;
			object obj2 = <>O.<0>__LobbyLeft;
			if (obj2 == null)
			{
				ZPlayFabMatchmakeLobbyLeftCallback val2 = LobbyLeft;
				<>O.<0>__LobbyLeft = val2;
				obj2 = (object)val2;
			}
			ZPlayFabMatchmaking.LobbyLeft += (ZPlayFabMatchmakeLobbyLeftCallback)obj2;
		}

		internal static void Unregister()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			object obj = <>O.<0>__LobbyLeft;
			if (obj == null)
			{
				ZPlayFabMatchmakeLobbyLeftCallback val = LobbyLeft;
				<>O.<0>__LobbyLeft = val;
				obj = (object)val;
			}
			ZPlayFabMatchmaking.LobbyLeft -= (ZPlayFabMatchmakeLobbyLeftCallback)obj;
		}

		private static void LobbyLeft(bool success)
		{
			CharacterVaultPlugin.Log.LogDebug($"PlayFab teardown: LobbyLeft callback completed with success={success}.");
		}
	}
	internal static class PlayFabEndpointDiagnostics
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static OnRemotePlayerJoinedHandler <0>__PlayerJoined;

			public static OnRemotePlayerLeftHandler <1>__PlayerLeft;

			public static Func<PlayFabPlayer, string> <2>__DescribePlayer;
		}

		[ThreadStatic]
		private static string pendingSend;

		[ThreadStatic]
		private static string resolvedHandles;

		private static readonly HashSet<PlayFabMultiplayerManager> Observed = new HashSet<PlayFabMultiplayerManager>();

		internal static void Observe(PlayFabMultiplayerManager manager)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0054: Expected O, but got Unknown
			if (!((Object)(object)manager == (Object)null) && Observed.Add(manager))
			{
				object obj = <>O.<0>__PlayerJoined;
				if (obj == null)
				{
					OnRemotePlayerJoinedHandler val = PlayerJoined;
					<>O.<0>__PlayerJoined = val;
					obj = (object)val;
				}
				manager.OnRemotePlayerJoined += (OnRemotePlayerJoinedHandler)obj;
				object obj2 = <>O.<1>__PlayerLeft;
				if (obj2 == null)
				{
					OnRemotePlayerLeftHandler val2 = PlayerLeft;
					<>O.<1>__PlayerLeft = val2;
					obj2 = (object)val2;
				}
				manager.OnRemotePlayerLeft += (OnRemotePlayerLeftHandler)obj2;
				CharacterVaultPlugin.Log.LogDebug("PlayFab endpoint diagnostics started: " + DescribeManager(manager) + ".");
			}
		}

		internal static void BeginSend(PlayFabMultiplayerManager manager, IEnumerable<PlayFabPlayer> players, int payloadSize, DeliveryOption delivery)
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			pendingSend = "manager=" + DescribeManager(manager) + ", recipients=" + DescribePlayers(players) + ", " + $"payloadBytes={payloadSize}, delivery={delivery}";
			resolvedHandles = "not-resolved";
		}

		internal static void HandlesResolved(PARTY_ENDPOINT_HANDLE[] handles)
		{
			resolvedHandles = ((handles == null) ? "<null>" : string.Join(",", handles.Select((PARTY_ENDPOINT_HANDLE handle) => ((object)handle).ToString()).ToArray()));
		}

		internal static void EndSend(bool succeeded)
		{
			if (!succeeded)
			{
				CharacterVaultPlugin.Log.LogError("PlayFab SendDataMessage rejected: " + pendingSend + ", endpointHandles=" + resolvedHandles + ". Call stack:\n" + Environment.StackTrace);
			}
			pendingSend = null;
			resolvedHandles = null;
		}

		private static void PlayerJoined(object sender, PlayFabPlayer player)
		{
			LogPlayerEvent("joined", (PlayFabMultiplayerManager)((sender is PlayFabMultiplayerManager) ? sender : null), player);
		}

		private static void PlayerLeft(object sender, PlayFabPlayer player)
		{
			LogPlayerEvent("left", (PlayFabMultiplayerManager)((sender is PlayFabMultiplayerManager) ? sender : null), player);
		}

		private static void LogPlayerEvent(string action, PlayFabMultiplayerManager manager, PlayFabPlayer player)
		{
			CharacterVaultPlugin.Log.LogDebug("PlayFab remote player " + action + ": player=" + DescribePlayer(player) + ", " + DescribeManager(manager) + ".");
		}

		private static string DescribeManager(PlayFabMultiplayerManager manager)
		{
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			if ((Object)(object)manager == (Object)null)
			{
				return "manager=<null>";
			}
			return $"managerObject={RuntimeHelpers.GetHashCode(manager):X8}, state={manager.State}, " + "network=" + PlayFabConnectionDiagnostics.Fingerprint(manager.NetworkId) + ", remotePlayers=" + DescribePlayers(manager.RemotePlayers);
		}

		private static string DescribePlayers(IEnumerable<PlayFabPlayer> players)
		{
			if (players == null)
			{
				return "<null>";
			}
			return "[" + string.Join(",", players.Select(DescribePlayer).ToArray()) + "]";
		}

		private static string DescribePlayer(PlayFabPlayer player)
		{
			if (player == null)
			{
				return "<null>";
			}
			return PlayFabConnectionDiagnostics.Fingerprint(player.EntityKey?.Id) + "@" + $"{RuntimeHelpers.GetHashCode(player):X8}";
		}
	}
	[HarmonyPatch(typeof(PlayFabMultiplayerManager), "SendDataMessage", new Type[]
	{
		typeof(byte[]),
		typeof(IEnumerable<PlayFabPlayer>),
		typeof(DeliveryOption)
	})]
	internal static class CharacterVaultPlayFabSendDataDiagnosticsPatch
	{
		private static void Prefix(PlayFabMultiplayerManager __instance, byte[] buffer, IEnumerable<PlayFabPlayer> recipients, DeliveryOption deliveryOption)
		{
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			PlayFabEndpointDiagnostics.BeginSend(__instance, recipients, (buffer != null) ? buffer.Length : 0, deliveryOption);
		}

		private static void Postfix(bool __result)
		{
			PlayFabEndpointDiagnostics.EndSend(__result);
		}
	}
	[HarmonyPatch(typeof(PlayFabMultiplayerManager), "EndPointHandlesFromPlayFabPlayerListNoGC")]
	internal static class CharacterVaultPlayFabEndpointResolutionDiagnosticsPatch
	{
		private static void Postfix(PARTY_ENDPOINT_HANDLE[] __result)
		{
			PlayFabEndpointDiagnostics.HandlesResolved(__result);
		}
	}
	internal sealed class ProfileCommitQueue
	{
		private readonly Queue<PendingCommit> _commits = new Queue<PendingCommit>();

		private readonly object _lock = new object();

		private readonly VaultStorage _storage;

		private readonly SynchronizationContext _unityContext;

		private readonly Action<PendingCommit> _confirm;

		private bool _workerRunning;

		internal ProfileCommitQueue(VaultStorage storage, SynchronizationContext unityContext, Action<PendingCommit> confirm)
		{
			_storage = storage ?? throw new ArgumentNullException("storage");
			_unityContext = unityContext ?? throw new ArgumentNullException("unityContext");
			_confirm = confirm ?? throw new ArgumentNullException("confirm");
		}

		internal void Enqueue(PendingCommit commit)
		{
			lock (_lock)
			{
				_commits.Enqueue(commit);
				if (!_workerRunning)
				{
					_workerRunning = true;
					ThreadPool.QueueUserWorkItem(delegate
					{
						Process();
					});
				}
			}
		}

		private void Process()
		{
			while (true)
			{
				if (!TryDequeue(out var commit))
				{
					break;
				}
				try
				{
					_storage.Commit(commit.Session.AccountId, commit.Session.Name, commit.Data);
					_unityContext.Post(delegate
					{
						_confirm(commit);
					}, null);
				}
				catch (Exception arg)
				{
					CharacterVaultPlugin.Log.LogError($"Character vault commit failed: {arg}");
				}
			}
		}

		private bool TryDequeue(out PendingCommit commit)
		{
			lock (_lock)
			{
				commit = ((_commits.Count > 0) ? _commits.Dequeue() : null);
				if (commit == null)
				{
					_workerRunning = false;
				}
				return commit != null;
			}
		}
	}
	internal static class ProfileFile
	{
		internal static byte[] Read(PlayerProfile profile)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_000d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Expected O, but got Unknown
			FileReader val = new FileReader(profile.GetPath(), profile.m_fileSource, (FileHelperType)0);
			try
			{
				Stream baseStream = val.m_binary.BaseStream;
				baseStream.Position = 0L;
				byte[] array = new byte[baseStream.Length];
				int num;
				for (int i = 0; i < array.Length; i += num)
				{
					num = baseStream.Read(array, i, array.Length - i);
					if (num == 0)
					{
						throw new EndOfStreamException("The character profile ended unexpectedly.");
					}
				}
				return array;
			}
			finally
			{
				val.Dispose();
			}
		}

		internal static PlayerProfile ReplaceSelected(byte[] data)
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: 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_0050: 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_0069: Expected O, but got Unknown
			PlayerProfile playerProfile = Game.instance.GetPlayerProfile();
			string path = playerProfile.GetPath();
			BackupSelectedProfile(playerProfile, path);
			string text = path + ".runic-vault-new";
			Write(text, playerProfile.m_fileSource, data);
			SaveApiCompatibility.ReplaceOldFile(path, text, playerProfile.m_fileSource);
			SaveApiCompatibility.InvalidateCharacterCache();
			PlayerProfile val = new PlayerProfile(playerProfile.GetFilename(), playerProfile.m_fileSource);
			if (!val.Load())
			{
				throw new InvalidDataException("Valheim rejected the authoritative server profile.");
			}
			return val;
		}

		internal static void BackupSelectedProfile(PlayerProfile selected, string path)
		{
			try
			{
				byte[] array = Read(selected);
				string text = Path.Combine(Paths.ConfigPath, "RunicCharacterVault", "client-backups");
				Directory.CreateDirectory(text);
				string text2 = DateTime.UtcNow.ToString("yyyyMMdd'T'HHmmssfffffff'Z'", CultureInfo.InvariantCulture);
				string text3 = VaultStorage.SafeSegment(selected.GetFilename());
				string path2 = Path.Combine(text, text3 + "_" + text2 + ".fch");
				using (FileStream fileStream = new FileStream(path2, FileMode.CreateNew, FileAccess.Write, FileShare.None, 65536, FileOptions.WriteThrough))
				{
					fileStream.Write(array, 0, array.Length);
					fileStream.Flush(flushToDisk: true);
				}
				if (VaultStorage.Hash(File.ReadAllBytes(path2)) != VaultStorage.Hash(array))
				{
					throw new IOException("Local character backup verification failed.");
				}
				BackupRetention.Apply(text, text3);
			}
			catch (Exception ex)
			{
				CharacterVaultPlugin.Log?.LogWarning("Could not create the local pre-download character backup: " + ex.Message);
				throw new IOException("Character Vault stopped because it could not preserve your local character backup.", ex);
			}
		}

		private static void Write(string path, FileSource source, byte[] data)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Invalid comparison between Unknown and I4
			FileWriter obj = SaveApiCompatibility.CreateWriter(path, source);
			obj.m_binary.Write(data);
			obj.Finish();
			if ((int)obj.Status != 2)
			{
				throw new IOException("The authoritative character profile could not be written.");
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "SaveWorldAndPlayerProfiles")]
	internal static class CharacterVaultManualSavePatch
	{
		private static void Prefix()
		{
			CharacterVaultPlugin.Transfers?.SaveManualClientProfile();
		}
	}
	[HarmonyPatch(typeof(Minimap), "Start")]
	internal static class CharacterVaultSaveStatusPatch
	{
		private static void Postfix(Minimap __instance)
		{
			CharacterVaultPlugin.SaveStatus?.Attach(__instance);
		}
	}
	[HarmonyPatch(typeof(ZNet), "Start")]
	internal static class PendingExitRequestPatch
	{
		private static void Postfix(ZNet __instance)
		{
			if (__instance.IsServer())
			{
				CharacterVaultPlugin.Settings?.InitializeServer();
			}
			CharacterVaultPlugin.Coordinator?.ProcessPendingExitRequest();
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnNewConnection")]
	internal static class CharacterVaultConnectionPatch
	{
		private static void Postfix(ZNet __instance, ZNetPeer peer)
		{
			CharacterVaultPlugin.Transfers?.Register(__instance, peer);
		}
	}
	[HarmonyPatch(typeof(ZNet), "SendPeerInfo")]
	internal static class CharacterVaultHelloPatch
	{
		private static void Prefix(ZRpc rpc, bool __runOriginal)
		{
			if (__runOriginal)
			{
				ZNet instance = ZNet.instance;
				if (instance != null && !instance.IsServer())
				{
					CharacterVaultPlugin.Transfers?.SendHello(rpc);
				}
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	internal static class CharacterVaultAdmissionBarrierPatch
	{
		private static bool Prefix(ZRpc rpc, bool __runOriginal)
		{
			if (__runOriginal)
			{
				ZNet instance = ZNet.instance;
				if (instance != null && instance.IsServer())
				{
					return CharacterVaultPlugin.Transfers?.Approve(rpc) ?? false;
				}
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(ZNet), "IsAllowed")]
	internal static class CharacterVaultPermittedListReasonPatch
	{
		private static void Postfix(string hostName, string playerName, SyncedList ___m_bannedList, SyncedList ___m_permittedList, Platform ___m_steamPlatform, bool __result)
		{
			//IL_0019: Unknown result type (might be due to invalid IL or missing references)
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			CharacterVaultPlugin.Transfers?.RecordPermission(hostName, __result);
			if (!__result && !IsListed(___m_bannedList, hostName, ___m_steamPlatform) && !___m_bannedList.Contains(playerName) && ___m_permittedList.Count() != 0 && !IsListed(___m_permittedList, hostName, ___m_steamPlatform))
			{
				CharacterVaultRejection.RecordPermittedListRejection(hostName);
			}
		}

		private unsafe static bool IsListed(SyncedList list, string value, Platform steamPlatform)
		{
			//IL_000c: 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)
			//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_0037: Unknown result type (might be due to invalid IL or missing references)
			PlatformUserID val = default(PlatformUserID);
			if (!PlatformUserID.TryParse(value, ref val))
			{
				((PlatformUserID)(ref val))..ctor(steamPlatform, value);
			}
			if (!list.Contains(((object)(*(PlatformUserID*)(&val))/*cast due to .constrained prefix*/).ToString()))
			{
				if (val.m_platform == steamPlatform)
				{
					return list.Contains(val.m_userID.ToString());
				}
				return false;
			}
			return true;
		}
	}
	[HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")]
	internal static class CharacterVaultPermittedListMessagePatch
	{
		private static void Postfix(ZRpc rpc)
		{
			ZNet instance = ZNet.instance;
			if (instance != null && instance.IsServer())
			{
				CharacterVaultRejection.SendPermittedListRejection(rpc);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "Disconnect")]
	internal static class CharacterVaultDisconnectPatch
	{
		private static void Prefix(ZNetPeer peer)
		{
			ZNet instance = ZNet.instance;
			bool server = instance != null && instance.IsServer();
			string text = CharacterVaultPlugin.Transfers?.DescribeDisconnect(peer, server) ?? "state unavailable";
			CharacterVaultPlugin.Log?.LogDebug("Observed peer disconnection before CharacterVault cleanup: " + text + ".");
			CharacterVaultPlugin.Transfers?.Remove(peer);
			if (peer?.m_rpc != null)
			{
				CharacterVaultRejection.Remove(peer.m_rpc);
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	internal static class CharacterVaultClientNetworkDestroyPatch
	{
		private static void Prefix(ZNet __instance)
		{
			if (!__instance.IsServer())
			{
				ZNetPeer serverPeer = __instance.GetServerPeer();
				string text = CharacterVaultPlugin.Transfers?.DescribeDisconnect(serverPeer, server: false) ?? "state unavailable";
				CharacterVaultPlugin.Log?.LogDebug("Observed client network teardown before CharacterVault cleanup: " + text + ".");
			}
		}
	}
	[HarmonyPatch(typeof(FejdStartup), "ShowConnectError")]
	internal static class CharacterVaultPublishPlayFabFailurePatch
	{
		private static void Prefix()
		{
			PlayFabConnectionDiagnostics.PublishBlockingFailure();
		}
	}
	[HarmonyPatch(typeof(ZNet), "InternalKick", new Type[] { typeof(ZNetPeer) })]
	internal static class CharacterVaultKickBarrierPatch
	{
		private static bool Prefix(ZNet __instance, ZNetPeer peer)
		{
			return CharacterVaultPlugin.ServerDisconnects?.AllowKick(__instance, peer) ?? true;
		}
	}
	[HarmonyPatch(typeof(PlayerProfile), "SavePlayerToDisk")]
	internal static class CharacterVaultProfileSavedPatch
	{
		private static void Postfix(PlayerProfile __instance, bool __result)
		{
			if (__result)
			{
				CharacterVaultPlugin.Transfers?.UploadSavedProfile(__instance);
			}
		}
	}
	[HarmonyPatch(typeof(Game), "Logout")]
	internal static class CharacterVaultVoluntaryLogoutPatch
	{
		private static bool Prefix(Game __instance, bool save, bool changeToStartScene)
		{
			CharacterVaultPlugin.Log.LogDebug($"Game.Logout invoked: save={save}, changeToStartScene={changeToStartScene}, " + $"pendingCharacterSave={CharacterVaultPlugin.DisconnectCoordinator?.HasPendingSave ?? false}.");
			return CharacterVaultPlugin.DisconnectCoordinator?.AllowLogout(__instance, save, changeToStartScene) ?? true;
		}
	}
	[HarmonyPatch(typeof(Game), "ContinueLogout")]
	internal static class CharacterVaultContinueLogoutDiagnosticsPatch
	{
		private static void Prefix(bool save, bool shouldExit, bool changeToStartScene)
		{
			CharacterVaultPlugin.Log.LogDebug($"Game.ContinueLogout invoked: save={save}, shouldExit={shouldExit}, " + $"changeToStartScene={changeToStartScene}, " + $"pendingCharacterSave={CharacterVaultPlugin.DisconnectCoordinator?.HasPendingSave ?? false}.");
		}
	}
	[HarmonyPatch(typeof(Game), "OnDestroy")]
	internal static class CharacterVaultGameDestroyDiagnosticsPatch
	{
		private static void Prefix()
		{
			CharacterVaultPlugin.Log.LogDebug("Game.OnDestroy invoked: " + $"pendingCharacterSave={CharacterVaultPlugin.DisconnectCoordinator?.HasPendingSave ?? false}.");
		}
	}
	[HarmonyPatch(typeof(Menu), "OnLogout")]
	internal static class CharacterVaultLogoutButtonDiagnosticsPatch
	{
		private static void Prefix()
		{
			CharacterVaultPlugin.Log?.LogDebug("The in-game Disconnect button was pressed.");
		}
	}
	[HarmonyPatch(typeof(Menu), "QuitGame")]
	internal static class CharacterVaultMenuQuitPatch
	{
		private static bool Prefix()
		{
			return CharacterVaultPlugin.DisconnectCoordinator?.AllowMenuQuit() ?? true;
		}
	}
	[HarmonyPatch(typeof(Player), "OnSpawned")]
	internal static class CharacterVaultStartingItemsPatch
	{
		private static void Postfix(Player __instance)
		{
			CharacterVaultPlugin.DisconnectCoordinator?.RecordPlayerSpawned();
			CharacterVaultPlugin.Transfers?.RecordPlayerSpawned(__instance);
		}
	}
	[HarmonyPatch(typeof(Game), "SpawnPlayer")]
	internal static class CharacterVaultApplyProfilePatch
	{
		private static void Prefix(ref PlayerProfile ___m_playerProfile)
		{
			CharacterVaultPlugin.Transfers?.ApplyPendingProfile(ref ___m_playerProfile);
		}
	}
	[HarmonyPatch(typeof(ZNet), "Save")]
	internal static class CharacterVaultWorldSavePatch
	{
		private static void Prefix(ZNet __instance)
		{
			WorldSavePolicy.Handle(__instance.IsServer(), delegate
			{
				CharacterVaultPlugin.Transfers?.RequestWorldCheckpoint();
			});
		}
	}
	internal sealed class VaultSession
	{
		internal string AccountId { get; }

		internal long CharacterId { get; }

		internal string Name { get; }

		internal bool NewCharacter { get; }

		internal ServerProfileSessionState State { get; } = new ServerProfileSessionState();

		internal bool Enrolling { get; set; }

		internal VaultSession(string accountId, long characterId, string name, bool newCharacter)
		{
			AccountId = accountId;
			CharacterId = characterId;
			Name = name;
			NewCharacter = newCharacter;
		}
	}
	internal sealed class PendingCommit
	{
		internal byte[] Data { get; }

		internal ZRpc Rpc { get; }

		internal string RequestId { get; }

		internal VaultSession Session { get; }

		internal PendingCommit(ZRpc rpc, VaultSession session, string requestId, byte[] data)
		{
			Rpc = rpc;
			Session = session;
			RequestId = requestId;
			Data = data;
		}
	}
	internal static class ProfileTransferProtocol
	{
		internal const int ChunkSize = 65536;

		internal static ZPackage Begin(string transferId, int length, string hash)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0013: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(transferId);
			val.Write(length);
			val.Write(hash);
			return val;
		}

		internal static ZPackage Chunk(string transferId, byte[] data, int offset)
		{
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			int num = Math.Min(65536, data.Length - offset);
			byte[] array = new byte[num];
			Buffer.BlockCopy(data, offset, array, 0, num);
			ZPackage val = new ZPackage();
			val.Write(transferId);
			val.Write(offset);
			val.Write(array);
			return val;
		}
	}
	internal sealed class IncomingTransfer
	{
		private readonly byte[] _data;

		private readonly bool[] _blocks;

		private readonly string _hash;

		private readonly string _transferId;

		internal string RequestId { get; set; }

		private IncomingTransfer(string transferId, int length, string hash)
		{
			_transferId = transferId;
			_hash = hash;
			_data = new byte[length];
			_blocks = new bool[(length + 65536 - 1) / 65536];
		}

		internal static IncomingTransfer Create(ZPackage package, int maximumLength)
		{
			string text = package.ReadString();
			int num = package.ReadInt();
			string text2 = package.ReadString();
			if (string.IsNullOrWhiteSpace(text) || num <= 0 || num > maximumLength || text2.Length != 64)
			{
				throw new InvalidDataException("The profile transfer header is invalid.");
			}
			return new IncomingTransfer(text, num, text2);
		}

		internal void Add(ZPackage package)
		{
			string text = package.ReadString();
			int num = package.ReadInt();
			byte[] array = package.ReadByteArray();
			if (text != _transferId || num < 0 || num % 65536 != 0 || array.Length == 0 || array.Length > 65536 || num + array.Length > _data.Length)
			{
				throw new InvalidDataException("The profile transfer chunk is invalid.");
			}
			int num2 = num / 65536;
			if (_blocks[num2])
			{
				throw new InvalidDataException("The profile transfer contains a duplicate chunk.");
			}
			Buffer.BlockCopy(array, 0, _data, num, array.Length);
			_blocks[num2] = true;
		}

		internal byte[] Complete(string transferId)
		{
			if (transferId != _transferId || _blocks.Any((bool block) => !block) || !string.Equals(VaultStorage.Hash(_data), _hash, StringComparison.OrdinalIgnoreCase))
			{
				throw new InvalidDataException("The profile transfer is incomplete or corrupted.");
			}
			return _data;
		}
	}
	internal sealed class ProfileTransferService : IDisposable
	{
		internal const string HelloRpc = "RunicCharacterVault_Hello_v1";

		internal const string AdmissionRpc = "RunicCharacterVault_Admission_v1";

		internal const string DownloadBeginRpc = "RunicCharacterVault_DownloadBegin_v1";

		internal const string DownloadChunkRpc = "RunicCharacterVault_DownloadChunk_v1";

		internal const string DownloadCompleteRpc = "RunicCharacterVault_DownloadComplete_v1";

		internal const string UploadBeginRpc = "RunicCharacterVault_UploadBegin_v1";

		internal const string UploadChunkRpc = "RunicCharacterVault_UploadChunk_v1";

		internal const string UploadCompleteRpc = "RunicCharacterVault_UploadComplete_v1";

		internal const string SaveRequestRpc = "RunicCharacterVault_SaveRequest_v1";

		internal const string SaveAckRpc = "RunicCharacterVault_SaveAck_v1";

		internal const string CommitAckRpc = "RunicCharacterVault_CommitAck_v1";

		private const int MaximumProfileBytes = 67108864;

		private readonly Dictionary<ZRpc, VaultSession> _sessions = new Dictionary<ZRpc, VaultSession>();

		private readonly Dictionary<ZRpc, IncomingTransfer> _uploads = new Dictionary<ZRpc, IncomingTransfer>();

		private readonly Dictionary<string, ZRpc> _enrollments = new Dictionary<string, ZRpc>(StringComparer.Ordinal);

		private readonly ClientSaveLifecycle _clientLifecycle = new ClientSaveLifecycle();

		private readonly VaultStorage _storage = new VaultStorage();

		private readonly CharacterAdmissionEvaluator _admission;

		private readonly ProfileCommitQueue _commits;

		private readonly ServerFinalSaveMonitor _finalSaveMonitor = new ServerFinalSaveMonitor();

		private IncomingTransfer _download;

		private bool _clientUploadBusy;

		private bool _suppressNextClientUpload;

		private string _pendingRequest;

		private PlayerProfile _pendingProfile;

		private IReadOnlyList<StartingItem> _serverStartingItems = Array.Empty<StartingItem>();

		internal ProfileTransferService(SynchronizationContext unityContext)
		{
			if (unityContext == null)
			{
				throw new ArgumentNullException("unityContext");
			}
			_admission = new CharacterAdmissionEvaluator(_storage);
			_commits = new ProfileCommitQueue(_storage, unityContext, ConfirmBackgroundCommit);
		}

		internal void Register(ZNet network, ZNetPeer peer)
		{
			if (network.IsServer())
			{
				RegisterServer(peer.m_rpc);
			}
			else
			{
				RegisterClient(peer.m_rpc);
			}
		}

		internal void SendHello(ZRpc serverRpc)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Expected O, but got Unknown
			Game instance = Game.instance;
			PlayerProfile val = ((instance != null) ? instance.GetPlayerProfile() : null);
			if (val != null)
			{
				ZPackage val2 = new ZPackage();
				val2.Write(val.GetPlayerID());
				val2.Write(val.GetName());
				bool flag = NewCharacterPolicy.HasNeverJoinedAWorld(val);
				if (!flag)
				{
					ProfileFile.BackupSelectedProfile(val, val.GetPath());
				}
				val2.Write(flag);
				serverRpc.Invoke("RunicCharacterVault_Hello_v1", new object[1] { val2 });
			}
		}

		internal bool Approve(ZRpc rpc)
		{
			if (!_sessions.TryGetValue(rpc, out var value))
			{
				Reject(rpc, "Character verification did not complete. Please try again.");
				return false;
			}
			if (value.State.Verified)
			{
				return value.State.Admitted;
			}
			value.State.Verified = true;
			if (_storage.TryRead(value.AccountId, value.Name, out var data))
			{
				SendDownload(rpc, value, data);
				value.State.Admitted = true;
				return true;
			}
			CharacterRestoreResult characterRestoreResult = TryRestore(value);
			if (characterRestoreResult != null && characterRestoreResult.Status == CharacterRestoreStatus.Restored)
			{
				if (!ValidateProfile(rpc, value, characterRestoreResult.Profile))
				{
					return false;
				}
				_storage.Commit(value.AccountId, value.Name, characterRestoreResult.Profile);
				SendDownload(rpc, value, characterRestoreResult.Profile);
				value.State.Admitted = true;
				return true;
			}
			if (characterRestoreResult != null && characterRestoreResult.Status == CharacterRestoreStatus.Failed)
			{
				_sessions.Remove(rpc);
				Reject(rpc, "Your saved character could not be restored right now. Please try again in a moment.");
				return false;
			}
			value.State.Admitted = AdmitEnrollment(rpc, value);
			return value.State.Admitted;
		}

		internal void RecordPermission(string hostName, bool permitted)
		{
			foreach (VaultSession item in _sessions.Values.Where((VaultSession candidate) => string.Equals(candidate.AccountId, hostName, StringComparison.Ordinal)))
			{
				item.State.RecordPermission(permitted);
			}
		}

		internal void Remove(ZNetPeer peer)
		{
			if (peer?.m_rpc != null)
			{
				if (_sessions.TryGetValue(peer.m_rpc, out var value))
				{
					_finalSaveMonitor.RecordRemoved(peer.m_rpc, value.Name);
				}
				_sessions.Remove(peer.m_rpc);
				_uploads.Remove(peer.m_rpc);
				ReleaseEnrollment(peer.m_rpc);
				CharacterVaultPlugin.ServerDisconnects?.RecordDisconnected(peer.m_rpc);
				ZNet instance = ZNet.instance;
				if (instance != null && !instance.IsServer())
				{
					ResetClientState();
				}
			}
		}

		internal string DescribeDisconnect(ZNetPeer peer, bool server)
		{
			ZRpc val = peer?.m_rpc;
			VaultSession value = null;
			bool flag = val != null && _sessions.TryGetValue(val, out value);
			ServerProfileSessionState serverProfileSessionState = (flag ? value.State : null);
			bool flag2 = ((!server) ? (CharacterVaultPlugin.DisconnectCoordinator?.HasPendingSave ?? false) : (CharacterVaultPlugin.ServerDisconnects?.HasPendingSave(val) ?? false));
			return string.Format("side={0}, peerReady={1}, ", server ? "server" : "client", peer != null && peer.IsReady()) + $"sessionTracked