Decompiled source of BeyondMastery v0.1.0

BepInEx/plugins/BeyondMastery/BeyondMastery.dll

Decompiled 10 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Security.Cryptography;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BeyondMastery.API;
using BeyondMastery.Core;
using BeyondMastery.Game;
using BeyondMastery.Persistence;
using BeyondMastery.UI;
using EntityStates;
using Facepunch.Steamworks;
using HG;
using On.RoR2;
using On.RoR2.UI.LogBook;
using ProperSave;
using R2API;
using RoR2;
using RoR2.Achievements;
using RoR2.ContentManagement;
using RoR2.ExpansionManagement;
using RoR2.Items;
using RoR2.Networking;
using RoR2.SolusWing;
using RoR2.UI;
using RoR2.UI.LogBook;
using RoR2BepInExPack.VanillaFixes;
using TMPro;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyVersion("0.0.0.0")]
namespace BeyondMastery.UI
{
	internal static class NativeUI
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static UnityAction <>9__7_0;

			public static Func<SurvivorIdentity, bool> <>9__15_0;

			internal void <Buttons>b__7_0()
			{
				Open(LocalUserManager.GetFirstLocalUser());
			}

			internal bool <Eclipse>b__15_0(SurvivorIdentity s)
			{
				if (Object.op_Implicit((Object)(object)s.Definition))
				{
					return !s.Definition.hidden;
				}
				return false;
			}
		}

		private static readonly List<GameObject> owned = new List<GameObject>();

		private static GuideDefinition guide;

		private static readonly Dictionary<LocalUser, bool> tracking = new Dictionary<LocalUser, bool>();

		private static readonly Dictionary<CharacterMaster, string> trackerStrings = new Dictionary<CharacterMaster, string>();

		internal static string TrackerFor(CharacterMaster master)
		{
			if (!Object.op_Implicit((Object)(object)master) || !trackerStrings.TryGetValue(master, out var value))
			{
				return "";
			}
			return value;
		}

		public static void Install()
		{
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			using (Stream stream = typeof(NativeUI).Assembly.GetManifestResourceStream("BeyondMastery.route.v1.json"))
			{
				using MemoryStream memoryStream = new MemoryStream();
				stream.CopyTo(memoryStream);
				guide = Codec.Decode<GuideDefinition>(memoryStream.ToArray());
			}
			LogBookController.BuildCategoriesButtons += new hook_BuildCategoriesButtons(Buttons);
			ObjectivePanelController.collectObjectiveSources += Objectives;
		}

		public static void Uninstall()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			LogBookController.BuildCategoriesButtons -= new hook_BuildCategoriesButtons(Buttons);
			ObjectivePanelController.collectObjectiveSources -= Objectives;
			foreach (GameObject item in owned)
			{
				if (Object.op_Implicit((Object)(object)item))
				{
					Object.Destroy((Object)(object)item);
				}
			}
			owned.Clear();
			tracking.Clear();
			trackerStrings.Clear();
			ObservedRouteSource.Clear();
		}

		private static void Buttons(orig_BuildCategoriesButtons orig, LogBookController self)
		{
			//IL_0077: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Expected O, but got Unknown
			//IL_009a: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a5: Expected O, but got Unknown
			orig.Invoke(self);
			if (Object.op_Implicit((Object)(object)((Transform)self.categoryContainer).Find("BeyondMasteryEntry")))
			{
				return;
			}
			GameObject val = Object.Instantiate<GameObject>(self.categoryButtonPrefab, (Transform)(object)self.categoryContainer);
			((Object)val).name = "BeyondMasteryEntry";
			owned.Add(val);
			LanguageTextMeshController componentInChildren = val.GetComponentInChildren<LanguageTextMeshController>();
			if (Object.op_Implicit((Object)(object)componentInChildren))
			{
				LanguageAPI.Add("BM_MENU", "Beyond Mastery");
				componentInChildren.token = "BM_MENU";
			}
			MPButton component = val.GetComponent<MPButton>();
			((Button)component).onClick = new ButtonClickedEvent();
			ButtonClickedEvent onClick = ((Button)component).onClick;
			object obj = <>c.<>9__7_0;
			if (obj == null)
			{
				UnityAction val2 = delegate
				{
					Open(LocalUserManager.GetFirstLocalUser());
				};
				<>c.<>9__7_0 = val2;
				obj = (object)val2;
			}
			((UnityEvent)onClick).AddListener((UnityAction)obj);
		}

		[ConCommand(/*Could not decode attribute arguments.*/)]
		private static void Command(ConCommandArgs args)
		{
			Open(((ConCommandArgs)(ref args)).GetSenderLocalUser());
		}

		private static SimpleDialogBox Dialog(LocalUser user, string title, string body)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_001d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0026: 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)
			SimpleDialogBox val = SimpleDialogBox.Create(user.eventSystem);
			val.headerToken = new TokenParamsPair
			{
				token = title
			};
			val.descriptionToken = new TokenParamsPair
			{
				token = body
			};
			owned.Add(val.rootObject);
			return val;
		}

		private static int Disclosure(LocalUser user)
		{
			Ledger ledger = Plugin.Instance.GetLedger(user);
			if (ledger == null || !ledger.State.Settings.TryGetValue("disclosure", out var value) || !int.TryParse(value, out var result))
			{
				return 0;
			}
			return result;
		}

		private static void SetDisclosure(LocalUser u, int value)
		{
			try
			{
				Plugin.Instance.GetLedger(u)?.SetSetting("disclosure", value.ToString());
			}
			catch (Exception ex)
			{
				Plugin.Instance.Warn("Guide preference not saved: " + ex.Message);
			}
		}

		private static string Label(LocalUser u, Challenge c, int index)
		{
			if (Disclosure(u) <= 0)
			{
				return "Challenge " + (index + 1);
			}
			return c.Name;
		}

		internal static void Open(LocalUser user, int page = 0, int filter = 0, bool modded = false)
		{
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			//IL_015a: Expected O, but got Unknown
			//IL_0183: Unknown result type (might be due to invalid IL or missing references)
			//IL_0188: Unknown result type (might be due to invalid IL or missing references)
			//IL_0193: Unknown result type (might be due to invalid IL or missing references)
			//IL_019d: Unknown result type (might be due to invalid IL or missing references)
			//IL_01a8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01b2: 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)
			//IL_01d1: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ed: Unknown result type (might be due to invalid IL or missing references)
			//IL_0202: Unknown result type (might be due to invalid IL or missing references)
			//IL_0124: Unknown result type (might be due to invalid IL or missing references)
			//IL_028a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0294: Expected O, but got Unknown
			//IL_047b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0490: Expected O, but got Unknown
			//IL_049e: Unknown result type (might be due to invalid IL or missing references)
			//IL_04b3: Expected O, but got Unknown
			//IL_04c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_04f6: Expected O, but got Unknown
			//IL_0504: Unknown result type (might be due to invalid IL or missing references)
			//IL_0528: Expected O, but got Unknown
			//IL_0536: Unknown result type (might be due to invalid IL or missing references)
			//IL_054b: Expected O, but got Unknown
			//IL_0559: Unknown result type (might be due to invalid IL or missing references)
			//IL_056e: Expected O, but got Unknown
			//IL_0439: Unknown result type (might be due to invalid IL or missing references)
			//IL_0443: Expected O, but got Unknown
			if (user == null)
			{
				return;
			}
			Grant[] grants = Plugin.Instance.GetLedger(user)?.State.Grants.Values.ToArray() ?? Array.Empty<Grant>();
			SurvivorIdentity[] array = (from survivorIdentity in BeyondMasteryAPI.Survivors
				where survivorIdentity.Official != modded && ((Object.op_Implicit((Object)(object)survivorIdentity.Definition) && !survivorIdentity.Definition.hidden) || grants.Any((Grant g) => g.Survivor == survivorIdentity.Key))
				where filter == 0 || (filter == 1 && grants.Count((Grant g) => g.Survivor == survivorIdentity.Key) < 10) || (filter == 2 && grants.Count((Grant g) => g.Survivor == survivorIdentity.Key) == 10)
				select survivorIdentity).ToArray();
			page = Math.Max(0, Math.Min(page, Math.Max(0, (array.Length - 1) / 4)));
			SimpleDialogBox d = Dialog(user, "Beyond Mastery", $"{grants.Length} earned achievements · {array.Length} survivor rows in this view\nSolus Heart: Collective Accept / Collective Reject.\nSelect a cell for requirements, cosmetic availability and ? help.\n{Plugin.Instance.Status}");
			Transform parent = ((TMP_Text)d.descriptionLabel).transform.parent;
			RectTransform val = (RectTransform)(object)((parent is RectTransform) ? parent : null);
			if (Object.op_Implicit((Object)(object)val))
			{
				val.sizeDelta = new Vector2(1120f, 700f);
			}
			GameObject val2 = new GameObject("BeyondMasteryMatrix", new Type[2]
			{
				typeof(RectTransform),
				typeof(GridLayoutGroup)
			});
			val2.transform.SetParent(((TMP_Text)d.descriptionLabel).transform.parent, false);
			RectTransform val3 = (RectTransform)val2.transform;
			val3.anchorMin = new Vector2(0f, 0f);
			val3.anchorMax = new Vector2(1f, 1f);
			val3.offsetMin = new Vector2(20f, 130f);
			val3.offsetMax = new Vector2(-20f, -150f);
			GridLayoutGroup component = val2.GetComponent<GridLayoutGroup>();
			component.cellSize = new Vector2(94f, 64f);
			component.spacing = new Vector2(3f, 5f);
			component.constraint = (Constraint)1;
			component.constraintCount = 11;
			Cell(d, val2.transform, "Survivor", null);
			for (int num = 0; num < 10; num++)
			{
				Challenge c = Catalogue.Challenges[num];
				Cell(d, val2.transform, Label(user, c, num), (UnityAction)delegate
				{
					Help(user, c);
				});
			}
			foreach (SurvivorIdentity s in array.Skip(page * 4).Take(4))
			{
				string text = (Object.op_Implicit((Object)(object)s.Definition) ? Language.GetString(s.Definition.displayNameToken) : s.Key);
				Cell(d, val2.transform, text + "\n" + grants.Count((Grant g) => g.Survivor == s.Key) + "/10", null);
				Challenge[] challenges = Catalogue.Challenges;
				foreach (Challenge c2 in challenges)
				{
					Grant earned = grants.FirstOrDefault((Grant g) => g.Survivor == s.Key && g.Challenge == c2.Key);
					bool flag = BeyondMasteryAPI.Providers.Any((SkinProviderBinding p) => p.EntitlementId == Catalogue.Entitlement(s.Key, c2.Key));
					Cell(d, val2.transform, (earned == null) ? "[ ]" : (flag ? "[X]" : "[X]\nNo skin"), (UnityAction)delegate
					{
						Details(user, s, c2, earned);
					});
				}
			}
			d.AddActionButton((UnityAction)delegate
			{
				Object.Destroy((Object)(object)d.rootObject);
				Open(user, page - 1, filter, modded);
			}, "Previous", false, Array.Empty<object>());
			d.AddActionButton((UnityAction)delegate
			{
				Object.Destroy((Object)(object)d.rootObject);
				Open(user, page + 1, filter, modded);
			}, "Next", false, Array.Empty<object>());
			d.AddActionButton((UnityAction)delegate
			{
				Object.Destroy((Object)(object)d.rootObject);
				Open(user, 0, (filter + 1) % 3, modded);
			}, (new string[3] { "All", "Incomplete", "Complete" })[filter], false, Array.Empty<object>());
			d.AddActionButton((UnityAction)delegate
			{
				Object.Destroy((Object)(object)d.rootObject);
				Open(user, 0, filter, !modded);
			}, modded ? "Modded" : "Official", false, Array.Empty<object>());
			d.AddActionButton((UnityAction)delegate
			{
				Eclipse(user);
			}, "Eclipse", false, Array.Empty<object>());
			d.AddActionButton((UnityAction)delegate
			{
				Help(user, Catalogue.Find("all_major_bosses"));
			}, "?", false, Array.Empty<object>());
			d.AddCancelButton("Close", Array.Empty<object>());
		}

		private static void Cell(SimpleDialogBox d, Transform parent, string title, UnityAction action)
		{
			//IL_0015: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Expected O, but got Unknown
			GameObject val = Object.Instantiate<GameObject>(d.buttonPrefab, parent);
			MPButton component = val.GetComponent<MPButton>();
			((Button)component).onClick = new ButtonClickedEvent();
			if (action != null)
			{
				((UnityEvent)((Button)component).onClick).AddListener(action);
			}
			else
			{
				((Selectable)component).interactable = false;
			}
			LanguageTextMeshController[] componentsInChildren = val.GetComponentsInChildren<LanguageTextMeshController>();
			for (int i = 0; i < componentsInChildren.Length; i++)
			{
				Object.Destroy((Object)(object)componentsInChildren[i]);
			}
			TextMeshProUGUI componentInChildren = val.GetComponentInChildren<TextMeshProUGUI>();
			if (Object.op_Implicit((Object)(object)componentInChildren))
			{
				((TMP_Text)componentInChildren).text = title;
				((TMP_Text)componentInChildren).fontSize = 15f;
				((TMP_Text)componentInChildren).enableWordWrapping = true;
			}
		}

		private static void Eclipse(LocalUser user)
		{
			IEnumerable<string> values = from s in BeyondMasteryAPI.Survivors
				where Object.op_Implicit((Object)(object)s.Definition) && !s.Definition.hidden
				select Language.GetString(s.Definition.displayNameToken) + ": " + string.Join(" ", from i in Enumerable.Range(1, 8)
					select "E" + i + ((i <= EclipseRun.GetLocalUserSurvivorCompletedEclipseLevel(user, s.Definition)) ? " [X]" : " [ ]"));
			Dialog(user, "Eclipse · native progress", string.Join("\n", values) + "\nRead-only native completion view. Eclipse Skins playtests stay unlocked. No duplicate grants, dates, or run evidence are imported.").AddCancelButton("Close", Array.Empty<object>());
		}

		private static void Details(LocalUser user, SurvivorIdentity s, Challenge c, Grant g)
		{
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_011c: Expected O, but got Unknown
			//IL_0125: Unknown result type (might be due to invalid IL or missing references)
			//IL_013a: Expected O, but got Unknown
			//IL_0143: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Expected O, but got Unknown
			bool flag = Disclosure(user) > 0 || g != null;
			bool flag2 = BeyondMasteryAPI.Providers.Any((SkinProviderBinding p) => p.EntitlementId == Catalogue.Entitlement(s.Key, c.Key));
			string text = (flag ? c.Requirement : "A major encounter challenge. Use ? to choose how much to reveal.");
			text = text + "\nMinimum: Monsoon or official Eclipse. Credit stays with your selected survivor.\n" + ((g == null) ? "Incomplete." : ("Achievement complete. Cosmetic entitlement earned; " + (flag2 ? "skin provider installed." : "skin pack not installed.") + "\nProvenance: " + g.Provenance + " · " + g.Utc));
			if (c.Key == "all_major_bosses")
			{
				text += "\nRequires all eight in the same loaded timeline and a victorious ending.";
			}
			SimpleDialogBox obj = Dialog(user, flag ? c.Name : "Beyond Mastery requirement", text);
			obj.AddActionButton((UnityAction)delegate
			{
				SkinPreferences.RestorePreference(user, s);
			}, "Restore preferred skin", false, Array.Empty<object>());
			obj.AddActionButton((UnityAction)delegate
			{
				Help(user, c);
			}, "? Open guide", false, Array.Empty<object>());
			obj.AddActionButton((UnityAction)delegate
			{
				tracking[user] = !tracking.TryGetValue(user, out var value) || !value;
			}, "Track / Untrack", false, Array.Empty<object>());
			obj.AddCancelButton("Close", Array.Empty<object>());
		}

		private static void Help(LocalUser user, Challenge c)
		{
			//IL_0031: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Expected O, but got Unknown
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0064: Expected O, but got Unknown
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Expected O, but got Unknown
			SimpleDialogBox obj = Dialog(user, "Guide disclosure", "Choose how much to reveal. Hints and the full route reveal secret encounters, locations and endings. Your choice is saved for this profile.");
			obj.AddActionButton((UnityAction)delegate
			{
				SetDisclosure(user, 0);
				ShowHelp(user, c, 0);
			}, "Requirement only", true, Array.Empty<object>());
			obj.AddActionButton((UnityAction)delegate
			{
				SetDisclosure(user, 1);
				ShowHelp(user, c, 1);
			}, "Reveal hints", true, Array.Empty<object>());
			obj.AddActionButton((UnityAction)delegate
			{
				SetDisclosure(user, 2);
				ShowHelp(user, c, 2);
			}, "Reveal full route", true, Array.Empty<object>());
			obj.AddCancelButton("Cancel", Array.Empty<object>());
		}

		private static void ShowHelp(LocalUser u, Challenge c, int level, int page = 0)
		{
			//IL_0177: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Expected O, but got Unknown
			GuideEdge[] edges = guide.Edges.Where((GuideEdge e) => c.Key == "all_major_bosses" || e.Challenge == c.Key).ToArray();
			string body = ((level == 0) ? "Complete the indicated major encounter on Monsoon or official Eclipse. The Final Reckoning requires eight complete encounters on one survivor, in one run, then victory." : (c.Requirement + "\n" + ((edges.Length == 0) ? "Resolve the legitimate branch; Acceptance is separate from Rejection." : ((level == 1) ? edges[page].Hint : (edges[page].Details + "\n\nPrerequisites: " + string.Join(", ", edges[page].Prerequisites) + "\nCosts: " + edges[page].CostSource)))));
			SimpleDialogBox d = Dialog(u, "Beyond Mastery guide" + ((edges.Length > 1) ? $" · {page + 1}/{edges.Length}" : ""), body);
			if (edges.Length > 1 && level > 0)
			{
				d.AddActionButton((UnityAction)delegate
				{
					Object.Destroy((Object)(object)d.rootObject);
					ShowHelp(u, c, level, (page + 1) % edges.Length);
				}, "Next leg", false, Array.Empty<object>());
			}
			d.AddCancelButton("Close", Array.Empty<object>());
		}

		private static void Objectives(CharacterMaster master, List<ObjectiveSourceDescriptor> list)
		{
			//IL_00da: Unknown result type (might be due to invalid IL or missing references)
			//IL_0106: Unknown result type (might be due to invalid IL or missing references)
			LocalUser val = ((IEnumerable<LocalUser>)LocalUserManager.readOnlyLocalUsersList).FirstOrDefault((Func<LocalUser, bool>)((LocalUser u) => (Object)(object)u.cachedMaster == (Object)(object)master));
			if (val != null && tracking.TryGetValue(val, out var value) && value)
			{
				Plugin instance = Plugin.Instance;
				Ledger ledger = instance.GetLedger(val);
				ObservedRoute state = ObservedRouteSource.Read(val);
				GuideAdvice guideAdvice = GuideEngine.Evaluate(instance.ViewRun(val), ledger?.State.Profile, state, guide, Disclosure(val));
				trackerStrings[master] = $"Final Reckoning: {guideAdvice.Count}/8\n{guideAdvice.Next}" + (string.IsNullOrEmpty(guideAdvice.Prerequisite) ? "" : ("\n" + guideAdvice.Prerequisite));
				list.Add(new ObjectiveSourceDescriptor
				{
					source = (Object)(object)instance,
					master = master,
					objectiveType = typeof(MasteryObjective)
				});
			}
		}

		internal static void Bounds(RectTransform rect)
		{
			BeyondMasteryAPI.ReportNativeObjectiveBounds(rect);
		}
	}
	public sealed class MasteryObjective : ObjectiveTracker
	{
		public override string GenerateString()
		{
			return NativeUI.TrackerFor(base.sourceDescriptor.master);
		}

		public override bool IsDirty()
		{
			return base.cachedString != NativeUI.TrackerFor(base.sourceDescriptor.master);
		}

		public override void OnStripSet()
		{
			((ObjectiveTracker)this).OnStripSet();
			Transform transform = ((ObjectiveTracker)this).stripObject.transform;
			NativeUI.Bounds((RectTransform)(object)((transform is RectTransform) ? transform : null));
		}
	}
}
namespace BeyondMastery.Persistence
{
	public static class Codec
	{
		public static byte[] Encode<T>(T value)
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			using MemoryStream memoryStream = new MemoryStream();
			((XmlObjectSerializer)new DataContractJsonSerializer(typeof(T))).WriteObject((Stream)memoryStream, (object)value);
			return memoryStream.ToArray();
		}

		public static T Decode<T>(byte[] bytes)
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			using MemoryStream memoryStream = new MemoryStream(bytes);
			return (T)((XmlObjectSerializer)new DataContractJsonSerializer(typeof(T))).ReadObject((Stream)memoryStream);
		}

		public static T Copy<T>(T value)
		{
			return Decode<T>(Encode(value));
		}

		public static string Hash(byte[] bytes)
		{
			using SHA256 sHA = SHA256.Create();
			return BitConverter.ToString(sHA.ComputeHash(bytes)).Replace("-", "").ToLowerInvariant();
		}

		public static string Key(string value)
		{
			return Hash(Encoding.UTF8.GetBytes(value));
		}
	}
	public static class DurableOutbox
	{
		private const string Prefix = "network.outbox.v1.";

		private static string Key(Grant g)
		{
			return "network.outbox.v1." + Codec.Key(g.Player + "\n" + g.Id + "\n" + g.Run);
		}

		public static void Enqueue(Ledger ledger, Grant grant)
		{
			string key = Key(grant);
			if (!ledger.State.Unknown.ContainsKey(key))
			{
				if (ledger.State.Unknown.Keys.Count((string k) => k.StartsWith("network.outbox.v1.")) >= 512)
				{
					throw new InvalidOperationException("Durable host evidence outbox is full");
				}
				ledger.SetOpaque(key, Convert.ToBase64String(Codec.Encode(grant)));
			}
		}

		public static Grant[] ForPlayer(Ledger ledger, string player)
		{
			return (from p in ledger.State.Unknown
				where p.Key.StartsWith("network.outbox.v1.")
				select Codec.Decode<Grant>(Convert.FromBase64String(p.Value)) into g
				where g.Player == player
				select g).ToArray();
		}

		public static void Acknowledge(Ledger ledger, Grant grant)
		{
			ledger.RemoveOpaque(Key(grant));
		}
	}
	[DataContract]
	public sealed class LedgerState
	{
		[DataMember]
		public int Schema = 1;

		[DataMember]
		public string Profile;

		[DataMember]
		public long Sequence;

		[DataMember]
		public int Generation;

		[DataMember]
		public Dictionary<string, string> Decisions = new Dictionary<string, string>();

		[DataMember]
		public Dictionary<string, Grant> Grants = new Dictionary<string, Grant>();

		[DataMember]
		public HashSet<string> DeliveryClaims = new HashSet<string>();

		[DataMember]
		public Dictionary<string, string> Settings = new Dictionary<string, string>();

		[DataMember]
		public Dictionary<string, string> Unknown = new Dictionary<string, string>();
	}
	public sealed class Ledger : IDisposable
	{
		private readonly FileStream lease;

		public readonly string DirectoryPath;

		private readonly string journal;

		private readonly string snapshot;

		private readonly string backup;

		private bool damagedTail;

		public LedgerState State { get; private set; }

		public static string DefaultRoot => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "BeyondMastery", "RoR2");

		public static string ProfileKey(string account, string profile)
		{
			if (string.IsNullOrWhiteSpace(account) || string.IsNullOrWhiteSpace(profile))
			{
				throw new ArgumentException("A stable account and profile identity are required");
			}
			return Codec.Key(account + "\n" + profile);
		}

		public Ledger(string root, string profile)
		{
			Catalogue.ValidateKey(profile);
			DirectoryPath = Path.Combine(Path.GetFullPath(root), profile);
			Directory.CreateDirectory(DirectoryPath);
			lease = new FileStream(Path.Combine(DirectoryPath, "ledger.lock"), FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.None);
			journal = Path.Combine(DirectoryPath, "journal.v1");
			snapshot = Path.Combine(DirectoryPath, "snapshot.v1");
			backup = Path.Combine(DirectoryPath, "snapshot.previous.v1");
			try
			{
				State = Recover(profile);
				if (!File.Exists(snapshot) && !File.Exists(backup) && !File.Exists(journal))
				{
					Snapshot(State);
				}
			}
			catch
			{
				lease.Dispose();
				throw;
			}
		}

		private LedgerState Read(string line, string profile)
		{
			string[] array = line.Split('|');
			if (array.Length != 2)
			{
				throw new InvalidDataException("Truncated transaction");
			}
			byte[] bytes = Convert.FromBase64String(array[1]);
			if (Codec.Hash(bytes) != array[0])
			{
				throw new InvalidDataException("Checksum mismatch");
			}
			LedgerState ledgerState = Codec.Decode<LedgerState>(bytes);
			if (ledgerState.Schema != 1)
			{
				throw new NotSupportedException("Future ledger schema; preserved read-only");
			}
			if (ledgerState.Profile != profile || ledgerState.Grants == null || ledgerState.Settings == null || ledgerState.DeliveryClaims == null || ledgerState.Sequence < 0 || ledgerState.Generation < 0 || ledgerState.Decisions == null || ledgerState.Unknown == null)
			{
				throw new InvalidDataException("Invalid profile transaction");
			}
			return ledgerState;
		}

		private LedgerState Recover(string profile)
		{
			LedgerState ledgerState = null;
			bool flag = false;
			string[] array = new string[2] { backup, snapshot };
			foreach (string path in array)
			{
				if (!File.Exists(path))
				{
					continue;
				}
				flag = true;
				try
				{
					LedgerState ledgerState2 = Read(File.ReadAllText(path).Trim(), profile);
					if (ledgerState == null || ledgerState2.Sequence > ledgerState.Sequence)
					{
						ledgerState = ledgerState2;
					}
				}
				catch (NotSupportedException)
				{
					throw;
				}
				catch (FormatException)
				{
				}
				catch (InvalidDataException)
				{
				}
				catch (SerializationException)
				{
				}
			}
			if (File.Exists(journal))
			{
				flag = true;
				long num = -1L;
				bool flag2 = false;
				foreach (string item in File.ReadLines(journal))
				{
					if (flag2)
					{
						continue;
					}
					try
					{
						LedgerState ledgerState3 = Read(item, profile);
						if (ledgerState3.Sequence <= num)
						{
							throw new InvalidDataException("Non-monotonic journal");
						}
						num = ledgerState3.Sequence;
						if (ledgerState == null || ledgerState3.Sequence > ledgerState.Sequence)
						{
							ledgerState = ledgerState3;
						}
					}
					catch (NotSupportedException)
					{
						throw;
					}
					catch (Exception ex6) when (ex6 is FormatException || ex6 is InvalidDataException || ex6 is SerializationException)
					{
						damagedTail = true;
						flag2 = true;
					}
				}
			}
			if (ledgerState == null && flag)
			{
				throw new InvalidDataException("No valid ledger transaction. Originals preserved; export/recovery required.");
			}
			return ledgerState ?? new LedgerState
			{
				Profile = profile
			};
		}

		private static string Line(LedgerState state)
		{
			byte[] array = Codec.Encode(state);
			return Codec.Hash(array) + "|" + Convert.ToBase64String(array) + "\n";
		}

		private static void WriteFlush(string path, string data, FileMode mode)
		{
			using FileStream fileStream = new FileStream(path, mode, FileAccess.Write, FileShare.Read);
			byte[] bytes = Encoding.UTF8.GetBytes(data);
			fileStream.Write(bytes, 0, bytes.Length);
			fileStream.Flush(flushToDisk: true);
		}

		private void Snapshot(LedgerState s)
		{
			string text = snapshot + ".tmp";
			WriteFlush(text, Line(s), FileMode.Create);
			if (File.Exists(snapshot))
			{
				File.Replace(text, snapshot, backup);
			}
			else
			{
				File.Move(text, snapshot);
			}
		}

		private void Commit(LedgerState next)
		{
			next.Sequence = State.Sequence + 1;
			if (damagedTail)
			{
				Snapshot(State);
				File.Copy(journal, journal + ".damaged", overwrite: true);
				WriteFlush(journal, Line(State), FileMode.Create);
				damagedTail = false;
			}
			try
			{
				WriteFlush(journal, Line(next), FileMode.Append);
			}
			catch (IOException)
			{
				damagedTail = true;
				throw;
			}
			catch (UnauthorizedAccessException)
			{
				damagedTail = true;
				throw;
			}
			State = next;
			if (next.Sequence % 16 == 0L)
			{
				try
				{
					Snapshot(next);
					WriteFlush(journal, Line(next), FileMode.Create);
				}
				catch (IOException)
				{
				}
				catch (UnauthorizedAccessException)
				{
				}
			}
		}

		public bool Earn(Grant grant)
		{
			if (grant.Player != State.Profile || grant.Id != Catalogue.Achievement(grant.Survivor, grant.Challenge) || grant.Entitlement != Catalogue.Entitlement(grant.Survivor, grant.Challenge))
			{
				throw new InvalidDataException("Grant identity mismatch");
			}
			LedgerState ledgerState = Codec.Copy(State);
			if (grant.Challenge == "all_major_bosses" && ledgerState.Decisions.TryGetValue(grant.Run, out var value) && value == "collective_accept")
			{
				throw new InvalidDataException("This run lineage already resolved Acceptance");
			}
			if (grant.Challenge.StartsWith("collective_") && grant.Provenance == "encounter")
			{
				if (string.IsNullOrEmpty(grant.Run))
				{
					throw new InvalidDataException("Branch proof requires a real run ID");
				}
				if (ledgerState.Decisions.TryGetValue(grant.Run, out var value2) && value2 != grant.Challenge)
				{
					throw new InvalidDataException("Contradictory Collective outcome in the same run lineage");
				}
				if (!ledgerState.Decisions.ContainsKey(grant.Run))
				{
					ledgerState.Decisions.Add(grant.Run, grant.Challenge);
				}
			}
			if (State.Grants.ContainsKey(grant.Id))
			{
				if (ledgerState.Decisions.Count != State.Decisions.Count)
				{
					Commit(ledgerState);
				}
				return false;
			}
			ledgerState.Grants.Add(grant.Id, Codec.Copy(grant));
			Commit(ledgerState);
			return true;
		}

		public bool ClaimCelebration(string id)
		{
			if (!State.Grants.ContainsKey(id) || State.DeliveryClaims.Contains(id))
			{
				return false;
			}
			LedgerState ledgerState = Codec.Copy(State);
			ledgerState.DeliveryClaims.Add(id);
			Commit(ledgerState);
			return true;
		}

		public void SetOpaque(string key, string value)
		{
			LedgerState ledgerState = Codec.Copy(State);
			ledgerState.Unknown[key] = value;
			Commit(ledgerState);
		}

		public void RemoveOpaque(string key)
		{
			if (State.Unknown.ContainsKey(key))
			{
				LedgerState ledgerState = Codec.Copy(State);
				ledgerState.Unknown.Remove(key);
				Commit(ledgerState);
			}
		}

		public void SetSetting(string key, string value)
		{
			LedgerState ledgerState = Codec.Copy(State);
			ledgerState.Settings[key] = value;
			Commit(ledgerState);
		}

		public void Reset(string confirmation)
		{
			if (confirmation != "RESET " + State.Profile)
			{
				throw new InvalidOperationException("Explicit profile reset confirmation required");
			}
			LedgerState ledgerState = Codec.Copy(State);
			ledgerState.Generation++;
			ledgerState.Grants.Clear();
			ledgerState.DeliveryClaims.Clear();
			Commit(ledgerState);
		}

		public void Export(string path)
		{
			WriteFlush(path, Line(State), FileMode.CreateNew);
		}

		public void Import(string path)
		{
			LedgerState ledgerState = Read(File.ReadAllText(path).Trim(), State.Profile);
			if (ledgerState.Generation != State.Generation)
			{
				throw new InvalidDataException("Reset generation differs; refusing backup resurrection");
			}
			LedgerState ledgerState2 = Codec.Copy(State);
			foreach (KeyValuePair<string, string> decision in ledgerState.Decisions)
			{
				if (ledgerState2.Decisions.TryGetValue(decision.Key, out var value) && value != decision.Value)
				{
					throw new InvalidDataException("Imported branch contradicts stored lineage");
				}
				ledgerState2.Decisions[decision.Key] = decision.Value;
			}
			foreach (KeyValuePair<string, Grant> grant in ledgerState.Grants)
			{
				if (!ledgerState2.Grants.ContainsKey(grant.Key))
				{
					Grant g = Codec.Copy(grant.Value);
					if (g.Player != State.Profile || grant.Key != g.Id)
					{
						throw new InvalidDataException("Import profile or key mismatch");
					}
					if (g.Id.StartsWith("BeyondMastery.achievement.") && Catalogue.Challenges.Any((Challenge c) => c.Key == g.Challenge) && (g.Id != Catalogue.Achievement(g.Survivor, g.Challenge) || g.Entitlement != Catalogue.Entitlement(g.Survivor, g.Challenge)))
					{
						throw new InvalidDataException("Import identifier mismatch");
					}
					g.Provenance = "imported:" + g.Provenance;
					ledgerState2.Grants.Add(grant.Key, g);
					ledgerState2.DeliveryClaims.Add(grant.Key);
				}
			}
			foreach (KeyValuePair<string, string> item in ledgerState.Unknown)
			{
				if (!ledgerState2.Unknown.ContainsKey(item.Key))
				{
					ledgerState2.Unknown.Add(item.Key, item.Value);
				}
			}
			Commit(ledgerState2);
		}

		public void Dispose()
		{
			lease.Dispose();
		}
	}
	public static class Checkpoints
	{
		public static byte[] Save(RunState run)
		{
			RunState runState = Codec.Copy(run);
			runState.Checkpoint = Guid.NewGuid().ToString("N");
			return Codec.Encode(runState);
		}

		public static RunState Restore(byte[] data)
		{
			RunState runState = Codec.Decode<RunState>(data);
			if (runState.Schema != 1 || runState.Rules != "standard.monsoon-karma8.v1" || runState.Ended || string.IsNullOrEmpty(runState.Id) || runState.Players == null || runState.Evidence == null || runState.Evidence.Count > 512)
			{
				throw new InvalidDataException("Unsupported run checkpoint");
			}
			runState.ParentCheckpoint = runState.Checkpoint;
			runState.Timeline = Guid.NewGuid().ToString("N");
			return runState;
		}
	}
	public static class OfflineIdentity
	{
		public static string LoadOrCreate(string root)
		{
			Directory.CreateDirectory(root);
			string path = Path.Combine(root, "offline-account.uuid");
			if (!File.Exists(path))
			{
				try
				{
					using FileStream fileStream = new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.None);
					byte[] bytes = Encoding.UTF8.GetBytes(Guid.NewGuid().ToString("D"));
					fileStream.Write(bytes, 0, bytes.Length);
					fileStream.Flush(flushToDisk: true);
				}
				catch (IOException)
				{
					if (!File.Exists(path))
					{
						throw;
					}
				}
			}
			if (!Guid.TryParse(File.ReadAllText(path).Trim(), out var result))
			{
				throw new InvalidDataException("Offline account UUID is damaged; restore it instead of generating a new identity");
			}
			return "offline:" + result.ToString("D");
		}
	}
}
namespace BeyondMastery.Game
{
	internal sealed class EncounterAdapters : IDisposable
	{
		private sealed class Active
		{
			public string Key;

			public string Id;

			public string Scene;

			public int Machine;

			public int Karma;

			public HashSet<string> Phases = new HashSet<string>();

			public HashSet<string> Players = new HashSet<string>();

			public bool Valid = true;
		}

		private readonly Plugin plugin;

		private bool installed;

		private readonly Dictionary<string, Active> active = new Dictionary<string, Active>();

		private const string Brother = "EntityStates.Missions.BrotherEncounter.";

		private const string Prov = "EntityStates.Missions.ProvidenceBossEncounter.";

		private const string Harvester = "EntityStates.Missions.ChampRush.";

		private const string Meridian = "EntityStates.MeridianEvent.";

		private const string Wing = "EntityStates.SolusWing2.";

		private const string Heart = "EntityStates.SolusHeart.";

		private const string Scav = "EntityStates.Missions.LunarScavengerEncounter.";

		internal string ActiveChallenge => active.Values.FirstOrDefault()?.Key;

		internal bool HarvesterSecondPhase
		{
			get
			{
				if (active.TryGetValue("anima_harvester", out var value))
				{
					return value.Phases.Contains("EntityStates.Missions.ChampRush.ReaperFightSecondPhase");
				}
				return false;
			}
		}

		public EncounterAdapters(Plugin plugin)
		{
			this.plugin = plugin;
		}

		public void Install()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			//IL_0018: Unknown result type (might be due to invalid IL or missing references)
			//IL_0022: Expected O, but got Unknown
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_0033: Expected O, but got Unknown
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Expected O, but got Unknown
			EntityStateMachine.SetState += new hook_SetState(SetState);
			VoidRaidGauntletController.OnBeginEncounter += new hook_OnBeginEncounter(VoidBegin);
			VoidRaidGauntletController.SpawnOutroPortal += new hook_SpawnOutroPortal(VoidEnd);
			CharacterBody.onBodyStartGlobal += BodyStart;
			ScriptedCombatEncounter.BeginEncounter += new hook_BeginEncounter(ScriptedBegin);
			SceneManager.activeSceneChanged += SceneChanged;
			installed = true;
		}

		public void Clear()
		{
			active.Clear();
		}

		private void SceneChanged(Scene oldScene, Scene newScene)
		{
			Clear();
		}

		private static string Name(EntityState s)
		{
			return ((object)s)?.GetType().FullName ?? "";
		}

		private static object Field(object o, string name)
		{
			Type type = o?.GetType();
			while (type != null)
			{
				FieldInfo field = type.GetField(name, BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					return field.GetValue(o);
				}
				type = type.BaseType;
			}
			return null;
		}

		private static Type Expected(EntityState s)
		{
			object? obj = (((object)s)?.GetType().GetProperty("nextState", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))?.GetValue(s);
			return ((obj is EntityState) ? obj : null)?.GetType();
		}

		private void BodyStart(CharacterBody body)
		{
			if (!NetworkServer.active || !Object.op_Implicit((Object)(object)body) || !body.isPlayerControlled)
			{
				return;
			}
			foreach (Active value in active.Values)
			{
				Enroll(value);
			}
		}

		private void Enroll(Active a)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Invalid comparison between Unknown and I4
			//IL_005c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0061: Unknown result type (might be due to invalid IL or missing references)
			plugin.BindPlayers();
			foreach (NetworkUser readOnlyInstances in NetworkUser.readOnlyInstancesList)
			{
				if (!readOnlyInstances.isParticipating || !Object.op_Implicit((Object)(object)readOnlyInstances.master) || (int)readOnlyInstances.master.teamIndex != 1)
				{
					continue;
				}
				CharacterBody body = readOnlyInstances.master.GetBody();
				if (Object.op_Implicit((Object)(object)body) && !(((Component)body).gameObject.scene != SceneManager.GetActiveScene()))
				{
					string text = plugin.Player(readOnlyInstances);
					if (text != null)
					{
						a.Players.Add(text);
					}
				}
			}
		}

		private Active Start(string key, EntityStateMachine machine, string phase)
		{
			if (this.active.TryGetValue(key, out var value))
			{
				return value;
			}
			Active obj = new Active
			{
				Key = key,
				Id = Guid.NewGuid().ToString("N"),
				Machine = (Object.op_Implicit((Object)(object)machine) ? ((Object)machine).GetInstanceID() : 0)
			};
			SceneDef sceneDefForCurrentScene = SceneCatalog.GetSceneDefForCurrentScene();
			obj.Scene = ((sceneDefForCurrentScene != null) ? sceneDefForCurrentScene.baseSceneName : null) ?? "";
			obj.Karma = ((key == "mithrix_karma8" && Object.op_Implicit((Object)(object)KarmaManager.instance) && KarmaManager.instance.IsKarmaEnabled()) ? KarmaManager.instance.GetEffectiveKarma() : 0);
			Active active = obj;
			active.Phases.Add(phase);
			this.active.Add(key, active);
			Enroll(active);
			return active;
		}

		private void Complete(string key, string resolution)
		{
			if (active.TryGetValue(key, out var value) && value.Valid && plugin.Current != null)
			{
				active.Remove(key);
				plugin.Resolve(new Evidence
				{
					Encounter = value.Id,
					Challenge = key,
					Run = plugin.Current.Id,
					Timeline = plugin.Current.Timeline,
					Scene = value.Scene,
					Karma = value.Karma,
					Adapter = "ror2.1189835d-0fbe-45e7-8dc9-17f7e9a40d5b.v1",
					Resolution = resolution,
					Complete = true,
					Phases = value.Phases.OrderBy((string x) => x).ToArray(),
					Participants = value.Players.ToArray(),
					Utc = DateTime.UtcNow.ToString("o")
				});
			}
		}

		private void SetState(orig_SetState orig, EntityStateMachine self, EntityState next)
		{
			EntityState state = self.state;
			string before = Name(state);
			string after = Name(next);
			Type expected = null;
			bool finished = false;
			if (NetworkServer.active && plugin.Current != null && !plugin.Current.Ended)
			{
				try
				{
					expected = Expected(state);
					finished = Field(state, "finishedServer") as bool? == true || Field(state, "spawnedNextState") as bool? == true;
				}
				catch (Exception ex)
				{
					plugin.Warn("Phase adapter unavailable: " + ex.Message);
				}
			}
			orig.Invoke(self, next);
			if (!NetworkServer.active || plugin.Current == null || plugin.Current.Ended || self.state != next)
			{
				return;
			}
			try
			{
				Observe(self, state, before, after, expected, finished);
			}
			catch (Exception ex2)
			{
				plugin.Warn("Encounter evidence rejected: " + ex2.Message);
			}
		}

		private void Observe(EntityStateMachine self, EntityState previous, string before, string after, Type expected, bool finished)
		{
			//IL_0014: 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)
			SceneDef sceneDefForCurrentScene = SceneCatalog.GetSceneDefForCurrentScene();
			if (!Object.op_Implicit((Object)(object)sceneDefForCurrentScene) || ((Component)self).gameObject.scene != SceneManager.GetActiveScene())
			{
				return;
			}
			string[][] array = new string[3][]
			{
				new string[3] { "mithrix_karma8", "EntityStates.Missions.BrotherEncounter.", "moon2" },
				new string[3] { "false_son", "EntityStates.MeridianEvent.", "meridian" },
				new string[3] { "echo_of_providence", "EntityStates.Missions.ProvidenceBossEncounter.", "" }
			};
			foreach (string[] array2 in array)
			{
				string text = array2[0];
				string text2 = array2[1];
				if (!after.StartsWith(text2))
				{
					continue;
				}
				if ((array2[2] != "" && sceneDefForCurrentScene.baseSceneName != array2[2]) || (text == "echo_of_providence" && !Object.op_Implicit((Object)(object)((Component)self).GetComponent<ProvidenceEncounterController>())))
				{
					return;
				}
				if (after == text2 + "Phase1")
				{
					Start(text, self, after);
				}
				if (active.TryGetValue(text, out var value) && value.Machine == ((Object)self).GetInstanceID())
				{
					if (before.StartsWith(text2 + "Phase") && (expected != ((object)self.state).GetType() || !finished))
					{
						value.Valid = false;
					}
					value.Phases.Add(after);
					if ((after == text2 + "EncounterFinished" || after == text2 + "MeridianEventCleared") && (before == text2 + "BossDeath" || (text == "false_son" && expected == ((object)self.state).GetType() && finished)))
					{
						Complete(text, after);
					}
				}
				return;
			}
			if (after.StartsWith("EntityStates.Missions.ChampRush."))
			{
				ChampRushMissionController component = ((Component)self).GetComponent<ChampRushMissionController>();
				if (!Object.op_Implicit((Object)(object)component))
				{
					return;
				}
				if (after == "EntityStates.Missions.ChampRush.ReaperFight")
				{
					Start("anima_harvester", self, after);
				}
				if (active.TryGetValue("anima_harvester", out var value2) && value2.Machine == ((Object)self).GetInstanceID())
				{
					value2.Phases.Add(after);
					if (after == "EntityStates.Missions.ChampRush.Aftermath" && before == "EntityStates.Missions.ChampRush.ReaperFightSecondPhase" && value2.Phases.Contains("EntityStates.Missions.ChampRush.ReaperFight") && value2.Phases.Contains("EntityStates.Missions.ChampRush.ReaperSpawnSecondPhase") && Object.op_Implicit((Object)(object)component.reaperFirstPhase) && Object.op_Implicit((Object)(object)component.reaperSecondPhase) && component.reaperFirstPhase.combatSquad.memberCount == 0 && component.reaperSecondPhase.combatSquad.memberCount == 0)
					{
						Complete("anima_harvester", after);
					}
					if (after == "EntityStates.Missions.ChampRush.FailChampRush")
					{
						active.Remove("anima_harvester");
					}
				}
			}
			else if (after.StartsWith("EntityStates.SolusWing2."))
			{
				if (!Object.op_Implicit((Object)(object)((Component)self).GetComponent<SolutionalHauntMissionController2>()))
				{
					return;
				}
				if (after == "EntityStates.SolusWing2.Mission1Tunnel")
				{
					Start("solus_wing", self, after);
				}
				if (active.TryGetValue("solus_wing", out var value3) && value3.Machine == ((Object)self).GetInstanceID())
				{
					value3.Phases.Add(after);
					if (after == "EntityStates.SolusWing2.Mission5Death" && before == "EntityStates.SolusWing2.Mission4Desperation" && value3.Phases.Contains("EntityStates.SolusWing2.Mission2Fight") && value3.Phases.Contains("EntityStates.SolusWing2.Mission3Darkness"))
					{
						Complete("solus_wing", after);
					}
				}
			}
			else if (after.StartsWith("EntityStates.SolusHeart."))
			{
				SolusWebMissionController val = Object.FindObjectOfType<SolusWebMissionController>();
				if (!Object.op_Implicit((Object)(object)val))
				{
					return;
				}
				if ((Object)(object)val.phaseStateMachine == (Object)(object)self && after == "EntityStates.SolusHeart.Phase0.Mission0")
				{
					Start("collective", self, after);
				}
				if (!active.TryGetValue("collective", out var value4))
				{
					return;
				}
				if ((Object)(object)val.phaseStateMachine == (Object)(object)self)
				{
					value4.Phases.Add(after);
				}
				if (Object.op_Implicit((Object)(object)val.offeringMaster) && (Object)(object)((Component)self).gameObject == (Object)(object)val.offeringMaster.GetBodyObject() && value4.Phases.Contains("EntityStates.SolusHeart.Death.MissionCompleted"))
				{
					string text3 = ((after == "EntityStates.SolusHeart.Death.SolusHeartFinaleSequence+OfferingAccepted") ? "collective_accept" : ((after == "EntityStates.SolusHeart.Death.SolusHeartFinaleSequence+Death") ? "collective_reject" : null));
					if (text3 != null)
					{
						active.Remove("collective");
						value4.Key = text3;
						value4.Phases.Add(after);
						active[text3] = value4;
						Complete(text3, after);
					}
				}
			}
			else if (after.StartsWith("EntityStates.Missions.LunarScavengerEncounter.") && sceneDefForCurrentScene.baseSceneName == "limbo")
			{
				if (after == "EntityStates.Missions.LunarScavengerEncounter.WaitForAllMonstersDead" && Object.FindObjectsOfType<ScriptedCombatEncounter>().Any((ScriptedCombatEncounter c) => c.hasSpawnedServer && Object.op_Implicit((Object)(object)c.combatSquad) && c.combatSquad.readOnlyMembersList.Any((CharacterMaster m) => Object.op_Implicit((Object)(object)m) && LunarBody(m.GetBody()))))
				{
					Start("twisted_scavenger", self, after);
				}
				if (after == "EntityStates.Missions.LunarScavengerEncounter.FadeOut" && before == "EntityStates.Missions.LunarScavengerEncounter.WaitForAllMonstersDead" && TeamComponent.GetTeamMembers((TeamIndex)2).Count == 0)
				{
					Complete("twisted_scavenger", after);
				}
			}
		}

		private static bool LunarBody(CharacterBody body)
		{
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			if (Object.op_Implicit((Object)(object)body))
			{
				return new string[4] { "ScavLunar1Body", "ScavLunar2Body", "ScavLunar3Body", "ScavLunar4Body" }.Contains(BodyCatalog.GetBodyName(body.bodyIndex));
			}
			return false;
		}

		private void ScriptedBegin(orig_BeginEncounter orig, ScriptedCombatEncounter self)
		{
			orig.Invoke(self);
			if (!NetworkServer.active || plugin.Current == null)
			{
				return;
			}
			SceneDef sceneDefForCurrentScene = SceneCatalog.GetSceneDefForCurrentScene();
			if (!(((sceneDefForCurrentScene != null) ? sceneDefForCurrentScene.baseSceneName : null) != "limbo") && Object.op_Implicit((Object)(object)self.combatSquad) && self.combatSquad.readOnlyMembersList.Any((CharacterMaster m) => Object.op_Implicit((Object)(object)m) && LunarBody(m.GetBody())))
			{
				EntityStateMachine val = ((IEnumerable<EntityStateMachine>)Object.FindObjectsOfType<EntityStateMachine>()).FirstOrDefault((Func<EntityStateMachine, bool>)((EntityStateMachine m) => Name(m.state) == "EntityStates.Missions.LunarScavengerEncounter.WaitForAllMonstersDead"));
				if (Object.op_Implicit((Object)(object)val))
				{
					Start("twisted_scavenger", val, Name(val.state));
				}
			}
		}

		private void VoidBegin(orig_OnBeginEncounter orig, VoidRaidGauntletController self, ScriptedCombatEncounter encounter, int index)
		{
			orig.Invoke(self, encounter, index);
			if (NetworkServer.active && plugin.Current != null && !((Object)(object)self != (Object)(object)VoidRaidGauntletController.instance) && index >= 0 && index < self.phaseEncounters.Length && !((Object)(object)self.phaseEncounters[index] != (Object)(object)encounter))
			{
				Start("voidling", null, "phase:" + index).Phases.Add("phase:" + index);
			}
		}

		private void VoidEnd(orig_SpawnOutroPortal orig, VoidRaidGauntletController self)
		{
			orig.Invoke(self);
			if (NetworkServer.active && !((Object)(object)self != (Object)(object)VoidRaidGauntletController.instance) && active.TryGetValue("voidling", out var a) && self.phaseEncounters.Length != 0 && Enumerable.Range(0, self.phaseEncounters.Length).All((int i) => a.Phases.Contains("phase:" + i) && Object.op_Implicit((Object)(object)self.phaseEncounters[i]) && self.phaseEncounters[i].combatSquad.memberCount == 0))
			{
				Complete("voidling", "VoidRaidGauntletController.SpawnOutroPortal:all-phase-squads-resolved");
			}
		}

		public void Dispose()
		{
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Expected O, but got Unknown
			//IL_0021: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Expected O, but got Unknown
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_003c: Expected O, but got Unknown
			//IL_0054: Unknown result type (might be due to invalid IL or missing references)
			//IL_005e: Expected O, but got Unknown
			if (installed)
			{
				EntityStateMachine.SetState -= new hook_SetState(SetState);
				VoidRaidGauntletController.OnBeginEncounter -= new hook_OnBeginEncounter(VoidBegin);
				VoidRaidGauntletController.SpawnOutroPortal -= new hook_SpawnOutroPortal(VoidEnd);
				CharacterBody.onBodyStartGlobal -= BodyStart;
				ScriptedCombatEncounter.BeginEncounter -= new hook_BeginEncounter(ScriptedBegin);
				SceneManager.activeSceneChanged -= SceneChanged;
				Clear();
			}
		}
	}
	public sealed class NativeAchievement : BaseAchievement
	{
		public override void OnInstall()
		{
		}

		public override void OnUninstall()
		{
		}

		public override bool UserMeetsRequirements()
		{
			return false;
		}
	}
	internal sealed class NativeContent : IContentPackProvider, IDisposable
	{
		private readonly ContentPack pack = new ContentPack();

		internal readonly Dictionary<string, UnlockableDef> Unlockables = new Dictionary<string, UnlockableDef>();

		private static readonly Dictionary<string, string> Special = new Dictionary<string, string>
		{
			{ "Croco", "acrid" },
			{ "Engi", "engineer" },
			{ "Mage", "artificer" },
			{ "Merc", "mercenary" },
			{ "Toolbot", "mul_t" },
			{ "Treebot", "rex" },
			{ "Bandit2", "bandit" },
			{ "VoidSurvivor", "void_fiend" },
			{ "DroneTech", "operator" },
			{ "Enforcer2", "enforcer" },
			{ "FalseSon", "false_son" }
		};

		public string identifier => "com.rodanius.beyondmastery";

		public NativeContent()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			//IL_011f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Expected O, but got Unknown
			foreach (Type item in from t in typeof(Run).Assembly.GetTypes()
				where t.IsNested && t.Name == "Survivors" && (t.DeclaringType.Name == "RoR2Content" || t.DeclaringType.Name.StartsWith("DLC"))
				select t)
			{
				foreach (FieldInfo item2 in from f in item.GetFields(BindingFlags.Static | BindingFlags.Public)
					where f.FieldType == typeof(SurvivorDef)
					select f)
				{
					string value;
					string text = (Special.TryGetValue(item2.Name, out value) ? value : item2.Name.ToLowerInvariant());
					BeyondMasteryAPI.RegisterOfficial("ror2." + text, item.FullName + "::" + item2.Name);
				}
			}
			ContentManager.collectContentPackProviders += new CollectContentPackProvidersDelegate(Collect);
			SaferAchievementManager.OnCollectAchievementDefs += Achievements;
		}

		private void Collect(AddContentPackProviderDelegate add)
		{
			add.Invoke((IContentPackProvider)(object)this);
		}

		public IEnumerator LoadStaticContentAsync(LoadStaticContentAsyncArgs args)
		{
			BeyondMasteryAPI.Frozen = true;
			pack.identifier = identifier;
			foreach (SurvivorIdentity survivor in BeyondMasteryAPI.Survivors)
			{
				Challenge[] challenges = Catalogue.Challenges;
				foreach (Challenge challenge in challenges)
				{
					string text = Catalogue.Entitlement(survivor.Key, challenge.Key);
					UnlockableDef val = ScriptableObject.CreateInstance<UnlockableDef>();
					val.cachedName = text;
					val.nameToken = text + ".name";
					LanguageAPI.Add(val.nameToken, "Beyond Mastery cosmetic entitlement");
					val.getHowToUnlockString = () => "Complete the associated Beyond Mastery challenge on Monsoon or official Eclipse.";
					val.getUnlockedString = () => "Achievement complete. Cosmetic entitlement earned; skin pack availability is shown in Beyond Mastery.";
					Unlockables.Add(text, val);
				}
			}
			foreach (SkinProviderBinding provider in BeyondMasteryAPI.Providers)
			{
				provider.Skin.unlockableDef = Unlockables[provider.EntitlementId];
			}
			pack.unlockableDefs.Add(Unlockables.Values.ToArray());
			args.ReportProgress(1f);
			yield break;
		}

		public IEnumerator GenerateContentPackAsync(GetContentPackAsyncArgs args)
		{
			ContentPack.Copy(pack, args.output);
			args.ReportProgress(1f);
			yield break;
		}

		public IEnumerator FinalizeAsync(FinalizeAsyncArgs args)
		{
			args.ReportProgress(1f);
			yield break;
		}

		private void Achievements(List<string> identifiers, Dictionary<string, AchievementDef> map, List<AchievementDef> defs)
		{
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f5: 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_0119: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_0136: Expected O, but got Unknown
			//IL_01b6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c5: Unknown result type (might be due to invalid IL or missing references)
			ResolveRoster();
			foreach (SurvivorIdentity survivor in BeyondMasteryAPI.Survivors)
			{
				Challenge[] challenges = Catalogue.Challenges;
				foreach (Challenge challenge in challenges)
				{
					string text = Catalogue.Achievement(survivor.Key, challenge.Key);
					if (map.ContainsKey(text))
					{
						throw new InvalidOperationException("Duplicate achievement " + text);
					}
					LanguageAPI.Add(text + ".name", "Beyond Mastery: " + (Object.op_Implicit((Object)(object)survivor.Definition) ? Language.GetString(survivor.Definition.displayNameToken) : survivor.Key) + " / Challenge " + (Array.IndexOf(Catalogue.Challenges, challenge) + 1));
					LanguageAPI.Add(text + ".description", "Complete this survivor's major encounter objective on Monsoon or official Eclipse. Open Beyond Mastery for spoiler-controlled requirements and guidance.");
					AchievementDef val = new AchievementDef
					{
						identifier = text,
						unlockableRewardIdentifier = Catalogue.Entitlement(survivor.Key, challenge.Key),
						nameToken = text + ".name",
						descriptionToken = text + ".description",
						type = typeof(NativeAchievement),
						childAchievementIdentifiers = Array.Empty<string>()
					};
					if (Object.op_Implicit((Object)(object)survivor.Definition) && Object.op_Implicit((Object)(object)survivor.Definition.bodyPrefab))
					{
						CharacterBody component = survivor.Definition.bodyPrefab.GetComponent<CharacterBody>();
						if (Object.op_Implicit((Object)(object)component) && Object.op_Implicit((Object)(object)component.portraitIcon))
						{
							Texture portraitIcon = component.portraitIcon;
							Texture2D val2 = (Texture2D)(object)((portraitIcon is Texture2D) ? portraitIcon : null);
							if (Object.op_Implicit((Object)(object)val2))
							{
								val.SetAchievedIcon(Sprite.Create(val2, new Rect(0f, 0f, (float)((Texture)val2).width, (float)((Texture)val2).height), new Vector2(0.5f, 0.5f)));
							}
						}
					}
					identifiers.Add(text);
					map.Add(text, val);
					defs.Add(val);
				}
			}
		}

		internal static void ResolveRoster()
		{
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0084: Expected O, but got Unknown
			foreach (SurvivorIdentity s in BeyondMasteryAPI.Survivors)
			{
				if (s.NativeField != null)
				{
					string[] array = s.NativeField.Split(new string[1] { "::" }, StringSplitOptions.None);
					s.Definition = (SurvivorDef)typeof(Run).Assembly.GetType(array[0]).GetField(array[1]).GetValue(null);
				}
				else if (!Object.op_Implicit((Object)(object)s.Definition))
				{
					s.Definition = SurvivorCatalog.allSurvivorDefs.FirstOrDefault((Func<SurvivorDef, bool>)((SurvivorDef d) => Object.op_Implicit((Object)(object)d.bodyPrefab) && s.BodyAliases.Contains(((Object)d.bodyPrefab).name)));
				}
			}
		}

		public void Dispose()
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0011: Expected O, but got Unknown
			ContentManager.collectContentPackProviders -= new CollectContentPackProvidersDelegate(Collect);
			SaferAchievementManager.OnCollectAchievementDefs -= Achievements;
		}
	}
	internal sealed class NetworkBridge : IDisposable
	{
		private sealed class Peer
		{
			public NetworkConnection Connection;

			public NetworkUser User;

			public string Key;

			public string Profile;

			public string Nonce;

			public long Sequence;

			public Dictionary<long, Packet> Pending = new Dictionary<long, Packet>();
		}

		[DataContract]
		internal sealed class Packet
		{
			[DataMember]
			public int Version = 1;

			[DataMember]
			public string Kind;

			[DataMember]
			public string Profile;

			[DataMember]
			public string Player;

			[DataMember]
			public string Nonce;

			[DataMember]
			public uint User;

			[DataMember]
			public long Sequence;

			[DataMember]
			public Grant Grant;

			[DataMember]
			public RunState Run;
		}

		private sealed class Wire : MessageBase
		{
			public string Json;

			public override void Serialize(NetworkWriter writer)
			{
				writer.Write(Json);
			}

			public override void Deserialize(NetworkReader reader)
			{
				Json = reader.ReadString();
				if (Json == null || Json.Length > 30000)
				{
					throw new InvalidOperationException("Beyond Mastery packet too large");
				}
			}
		}

		private const short Up = 24771;

		private const short Down = 24772;

		private const int Protocol = 1;

		private readonly Plugin plugin;

		private bool serverRegistered;

		private float next;

		private int helloAttempts;

		private readonly HashSet<NetworkClient> clients = new HashSet<NetworkClient>();

		private readonly Dictionary<string, Peer> peers = new Dictionary<string, Peer>();

		private readonly Dictionary<string, string> nonces = new Dictionary<string, string>();

		private readonly Dictionary<string, long> lastSnapshot = new Dictionary<string, long>();

		private readonly Dictionary<string, RunState> clientRuns = new Dictionary<string, RunState>();

		private readonly HashSet<string> received = new HashSet<string>();

		internal RunState View(string profile)
		{
			if (profile == null || !clientRuns.TryGetValue(profile, out var value))
			{
				return null;
			}
			return value;
		}

		internal NetworkBridge(Plugin p)
		{
			plugin = p;
			NetworkManagerSystem.onStartServerGlobal += Server;
			NetworkManagerSystem.onStartClientGlobal += Client;
			NetworkManagerSystem.onStopClientGlobal += Stopped;
		}

		private void Send(NetworkConnection connection, short channel, Packet p)
		{
			byte[] array = Codec.Encode(p);
			if (array.Length > 21000)
			{
				plugin.Warn("Beyond Mastery evidence packet exceeds bound; export required");
				return;
			}
			connection.Send(channel, (MessageBase)(object)new Wire
			{
				Json = Convert.ToBase64String(array)
			});
		}

		private Packet Read(NetworkMessage msg)
		{
			Packet packet = Codec.Decode<Packet>(Convert.FromBase64String(msg.ReadMessage<Wire>().Json));
			if (packet.Version != 1)
			{
				throw new InvalidOperationException("Protocol mismatch");
			}
			return packet;
		}

		private void Server()
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			if (NetworkServer.handlers.ContainsKey(24771))
			{
				plugin.Warn("Beyond Mastery network channel already occupied; remote awards disabled");
				return;
			}
			NetworkServer.RegisterHandler((short)24771, new NetworkMessageDelegate(ServerMessage));
			serverRegistered = true;
		}

		private void Client(NetworkClient client)
		{
			//IL_0030: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Expected O, but got Unknown
			if (client.handlers.ContainsKey(24772))
			{
				plugin.Warn("Beyond Mastery client channel already occupied; remote awards disabled");
				return;
			}
			client.RegisterHandler((short)24772, new NetworkMessageDelegate(ClientMessage));
			clients.Add(client);
			helloAttempts = 0;
		}

		private void Stopped()
		{
			nonces.Clear();
			received.Clear();
			lastSnapshot.Clear();
			clientRuns.Clear();
			helloAttempts = 0;
		}

		internal string PlayerKey(NetworkUser user)
		{
			return peers.Values.FirstOrDefault((Peer p) => (Object)(object)p.User == (Object)(object)user)?.Key;
		}

		private void ServerMessage(NetworkMessage msg)
		{
			//IL_00bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f6: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				Packet p = Read(msg);
				NetworkUser val = ((IEnumerable<NetworkUser>)NetworkManagerSystem.GetConnectionNetworkUsers(msg.conn)).FirstOrDefault((Func<NetworkUser, bool>)delegate(NetworkUser u)
				{
					//IL_0001: Unknown result type (might be due to invalid IL or missing references)
					//IL_0006: Unknown result type (might be due to invalid IL or missing references)
					NetworkInstanceId netId = ((NetworkBehaviour)u).netId;
					return ((NetworkInstanceId)(ref netId)).Value == p.User;
				});
				if (!Object.op_Implicit((Object)(object)val))
				{
					return;
				}
				if (p.Kind == "hello")
				{
					if (p.Profile == null || p.Profile.Length != 64 || p.Profile.Any((char c) => !Uri.IsHexDigit(c)))
					{
						return;
					}
					string text = "remote." + Codec.Key(val.id.value + ":" + val.id.strValue + ":" + val.id.subId + ":" + p.Profile);
					if (!peers.TryGetValue(text, out var value))
					{
						if (peers.Count >= 64)
						{
							return;
						}
						value = new Peer
						{
							Key = text,
							Nonce = Guid.NewGuid().ToString("N"),
							Profile = p.Profile
						};
						peers.Add(text, value);
					}
					value.User = val;
					value.Connection = msg.conn;
					Send(msg.conn, 24772, new Packet
					{
						Kind = "hello",
						Profile = p.Profile,
						Player = text,
						Nonce = value.Nonce,
						User = p.User
					});
					plugin.BindPlayers();
					QueueState(value);
					Ledger ledger = HostLedger();
					if (ledger != null)
					{
						Grant[] array = DurableOutbox.ForPlayer(ledger, text);
						foreach (Grant grant in array)
						{
							QueueState(value, grant);
						}
					}
				}
				else
				{
					if (!(p.Kind == "ack") || !peers.TryGetValue(p.Player ?? "", out var value2) || value2.Connection != msg.conn || !((Object)(object)value2.User == (Object)(object)val) || !(value2.Nonce == p.Nonce))
					{
						return;
					}
					if (value2.Pending.TryGetValue(p.Sequence, out var value3) && value3.Grant != null)
					{
						Ledger ledger2 = HostLedger();
						if (ledger2 == null)
						{
							return;
						}
						DurableOutbox.Acknowledge(ledger2, value3.Grant);
					}
					value2.Pending.Remove(p.Sequence);
				}
			}
			catch (Exception ex)
			{
				plugin.Warn("Network evidence rejected: " + ex.GetType().Name);
			}
		}

		private void ClientMessage(NetworkMessage msg)
		{
			try
			{
				if (NetworkServer.active || !clients.Any((NetworkClient c) => c.connection == msg.conn))
				{
					return;
				}
				Packet p = Read(msg);
				if (((IEnumerable<LocalUser>)LocalUserManager.readOnlyLocalUsersList).FirstOrDefault((Func<LocalUser, bool>)delegate(LocalUser u)
				{
					//IL_0013: Unknown result type (might be due to invalid IL or missing references)
					//IL_0018: Unknown result type (might be due to invalid IL or missing references)
					if (Object.op_Implicit((Object)(object)u.currentNetworkUser))
					{
						NetworkInstanceId netId = ((NetworkBehaviour)u.currentNetworkUser).netId;
						if (((NetworkInstanceId)(ref netId)).Value == p.User)
						{
							return plugin.ProfileKeyFor(u) == p.Profile;
						}
					}
					return false;
				}) == null)
				{
					return;
				}
				if (p.Kind == "hello")
				{
					nonces[p.Profile] = p.Nonce;
					plugin.Status = "Negotiated authoritative host evidence";
				}
				else
				{
					if (p.Kind != "proof" || !nonces.TryGetValue(p.Profile, out var value) || value != p.Nonce)
					{
						return;
					}
					string item = p.Profile + ":" + p.Nonce + ":" + p.Sequence;
					if (!received.Contains(item))
					{
						if (p.Grant != null)
						{
							if (p.Grant.Player != p.Player || p.Grant.Provenance != "encounter" || p.Grant.Rules != "standard.monsoon-karma8.v1")
							{
								return;
							}
							Grant grant = Translate(p.Grant, p.Player, p.Profile);
							if (!plugin.Commit(grant))
							{
								return;
							}
						}
						if (p.Run != null && (!lastSnapshot.TryGetValue(p.Profile, out var value2) || p.Sequence > value2))
						{
							if (p.Run.Schema != 1 || p.Run.Players.Count != 1 || p.Run.Players[0].Player != p.Player || p.Run.Evidence.Count > 512)
							{
								return;
							}
							RunState runState = Codec.Copy(p.Run);
							runState.Players[0].Player = p.Profile;
							foreach (Evidence item2 in runState.Evidence)
							{
								item2.Participants = item2.Participants.Select((string x) => (!(x == p.Player)) ? x : p.Profile).ToArray();
							}
							clientRuns[p.Profile] = runState;
							lastSnapshot[p.Profile] = p.Sequence;
						}
						if (received.Count > 2048)
						{
							received.Clear();
						}
						received.Add(item);
					}
					Send(msg.conn, 24771, new Packet
					{
						Kind = "ack",
						User = p.User,
						Player = p.Player,
						Profile = p.Profile,
						Nonce = p.Nonce,
						Sequence = p.Sequence
					});
				}
			}
			catch (Exception ex)
			{
				plugin.Warn("Authoritative packet rejected: " + ex.GetType().Name);
			}
		}

		private static Grant Translate(Grant grant, string from, string to)
		{
			Grant grant2 = Codec.Copy(grant);
			grant2.Player = to;
			Evidence[] proof = grant2.Proof;
			foreach (Evidence obj in proof)
			{
				obj.Participants = (from p in obj.Participants
					where p == @from
					select to).ToArray();
			}
			return grant2;
		}

		private RunState Snapshot(Peer peer)
		{
			RunState current = plugin.Current;
			if (current == null || !current.Players.Any((Participant p) => p.Player == peer.Key))
			{
				return null;
			}
			RunState copy = Codec.Copy(current);
			copy.Players = copy.Players.Where((Participant p) => p.Player == peer.Key).ToList();
			copy.Evidence = (from e in copy.Evidence
				where e.Participants.Contains(peer.Key)
				group e by e.Challenge into g
				select g.FirstOrDefault((Evidence e) => copy.Qualifies(e)) ?? g.Last()).ToList();
			foreach (Evidence item in copy.Evidence)
			{
				item.Participants = new string[1] { peer.Key };
			}
			return copy;
		}

		private Ledger HostLedger()
		{
			return plugin.GetLedger(LocalUserManager.GetFirstLocalUser());
		}

		private void QueueState(Peer peer, Grant grant = null)
		{
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Unknown result type (might be due to invalid IL or missing references)
			if (peer.Pending.Count >= 128)
			{
				plugin.Warn("Remote progression queue full; reconnect/export required");
				return;
			}
			Packet obj = new Packet
			{
				Kind = "proof"
			};
			NetworkInstanceId netId = ((NetworkBehaviour)peer.User).netId;
			obj.User = ((NetworkInstanceId)(ref netId)).Value;
			obj.Player = peer.Key;
			obj.Profile = peer.Profile;
			obj.Nonce = peer.Nonce;
			obj.Sequence = ++peer.Sequence;
			obj.Grant = grant;
			obj.Run = Snapshot(peer);
			Packet packet = obj;
			peer.Pending.Add(packet.Sequence, packet);
			Send(peer.Connection, 24772, packet);
		}

		internal bool Queue(Grant g)
		{
			Ledger ledger = HostLedger();
			if (ledger == null)
			{
				return false;
			}
			DurableOutbox.Enqueue(ledger, g);
			if (peers.TryGetValue(g.Player, out var value))
			{
				QueueState(value, Codec.Copy(g));
			}
			return true;
		}

		internal void BroadcastState()
		{
			foreach (Peer item in peers.Values.Where((Peer p) => Object.op_Implicit((Object)(object)p.User) && p.Connection != null))
			{
				QueueState(item);
			}
		}

		internal void Tick()
		{
			//IL_0189: Unknown result type (might be due to invalid IL or missing references)
			//IL_018e: Unknown result type (might be due to invalid IL or missing references)
			if (Time.unscaledTime < next)
			{
				return;
			}
			next = Time.unscaledTime + 5f;
			if (NetworkServer.active)
			{
				foreach (Peer item in peers.Values.Where((Peer p) => Object.op_Implicit((Object)(object)p.User) && p.Connection != null))
				{
					foreach (Packet value in item.Pending.Values)
					{
						Send(item.Connection, 24772, value);
					}
				}
				return;
			}
			if (helloAttempts++ >= 6)
			{
				return;
			}
			foreach (NetworkClient item2 in clients.Where((NetworkClient c) => c != null && c.isConnected))
			{
				foreach (LocalUser readOnlyLocalUsers in LocalUserManager.readOnlyLocalUsersList)
				{
					string text = plugin.ProfileKeyFor(readOnlyLocalUsers);
					if (Object.op_Implicit((Object)(object)readOnlyLocalUsers.currentNetworkUser) && text != null && !nonces.ContainsKey(text))
					{
						NetworkConnection connection = item2.connection;
						Packet obj = new Packet
						{
							Kind = "hello"
						};
						NetworkInstanceId netId = ((NetworkBehaviour)readOnlyLocalUsers.currentNetworkUser).netId;
						obj.User = ((NetworkInstanceId)(ref netId)).Value;
						obj.Profile = text;
						Send(connection, 24771, obj);
					}
				}
			}
		}

		public void Dispose()
		{
			NetworkManagerSystem.onStartServerGlobal -= Server;
			NetworkManagerSystem.onStartClientGlobal -= Client;
			NetworkManagerSystem.onStopClientGlobal -= Stopped;
			if (serverRegistered)
			{
				NetworkServer.UnregisterHandler((short)24771);
			}
			foreach (NetworkClient client in clients)
			{
				if (client != null)
				{
					client.UnregisterHandler((short)24772);
				}
			}
			clients.Clear();
			peers.Clear();
		}
	}
	internal static class ObservedRouteSource
	{
		private sealed class Cached
		{
			public float At;

			public ObservedRoute Value;
		}

		private static readonly Dictionary<LocalUser, Cached> cache = new Dictionary<LocalUser, Cached>();

		internal static void Clear()
		{
			cache.Clear();
		}

		internal static ObservedRoute Read(LocalUser user)
		{
			//IL_0142: Unknown result type (might be due to invalid IL or missing references)
			//IL_0229: Unknown result type (might be due to invalid IL or missing references)
			if (cache.TryGetValue(user, out var value) && Time.unscaledTime - value.At < 1f)
			{
				return value.Value;
			}
			SceneDef scene = SceneCatalog.GetSceneDefForCurrentScene();
			CharacterBody cachedBody = user.cachedBody;
			ObservedRoute observedRoute = new ObservedRoute
			{
				Scene = (Object.op_Implicit((Object)(object)scene) ? scene.baseSceneName : ""),
				Karma = ((Object.op_Implicit((Object)(object)KarmaManager.instance) && KarmaManager.instance.IsKarmaEnabled()) ? KarmaManager.instance.GetEffectiveKarma() : 0),
				AtLunarExit = (Object.op_Implicit((Object)(object)scene) && scene.baseSceneName == "limbo"),
				AtHarvesterExit = (Object.op_Implicit((Object)(object)ChampRushMissionController.instance) && (Plugin.Instance.ViewRun(user)?.Evidence.Any((Evidence e) => e.Challenge == "anima_harvester" && e.Scene == (Object.op_Implicit((Object)(object)scene) ? scene.baseSceneName : "")) ?? false))
			};
			CursedBookBodyBehavior val = (Object.op_Implicit((Object)(object)cachedBody) ? ((Component)cachedBody).GetComponent<CursedBookBodyBehavior>() : null);
			CursedBookSpellDef die = CursedBookSpells.Die;
			if (Object.op_Implicit((Object)(object)val) && Object.op_Implicit((Object)(object)die) && Object.op_Implicit((Object)(object)die.itemDef))
			{
				SpellSlot spellSlotFromItemIndex = val.GetSpellSlotFromItemIndex(die.itemDef.itemIndex);
				if (spellSlotFromItemIndex != null)
				{
					observedRoute.ReincarnationReady = spellSlotFromItemIndex.uses > 0 && !spellSlotFromItemIndex.isConsumed;
					observedRoute.ReincarnationConsumed = spellSlotFromItemIndex.isConsumed;
				}
			}
			observedRoute.CanRecover = !observedRoute.AtLunarExit;
			if (observedRoute.Scene == "moon2")
			{
				FrogController val2 = Object.FindObjectOfType<FrogController>();
				if (Object.op_Implicit((Object)(object)val2) && Object.op_Implicit((Object)(object)val2.purchaseInteraction))
				{
					FieldInfo field = typeof(FrogController).GetField("petCount", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
					int num = ((!(field == null)) ? ((int)field.GetValue(val2)) : 0);
					observedRoute.FrogCost = $"Glass Frog: {Math.Max(0, val2.maxPets - num)} pets remaining; {val2.purchaseInteraction.cost} per pet ({val2.purchaseInteraction.costType}).";
				}
			}
			observedRoute.ActiveChallenge = Plugin.Instance.Encounters.ActiveChallenge;
			observedRoute.HarvesterSecondPhase = Plugin.Instance.Encounters.HarvesterSecondPhase;
			cache[user] = new Cached
			{
				At = Time.unscaledTime,
				Value = observedRoute
			};
			return observedRoute;
		}
	}
	[BepInPlugin("com.rodanius.beyondmastery", "Beyond Mastery", "0.1.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public sealed class Plugin : BaseUnityPlugin
	{
		internal NativeContent Content;

		internal RunState Current;

		internal EncounterAdapters Encounters;

		internal NetworkBridge Network;

		private readonly Dictionary<string, Ledger> ledgers = new Dictionary<string, Ledger>();

		private readonly Dictionary<LocalUser, string> profiles = new Dictionary<LocalUser, string>();

		private readonly List<Grant> pending = new List<Grant>();

		private ConfigEntry<string> root;

		private ConfigEntry<bool> offline;

		private float nextRetry;

		internal string Status = "Waiting for an authoritative run";

		internal const string SupportedMvid = "1189835d-0fbe-45e7-8dc9-17f7e9a40d5b";

		private bool supported;

		private bool properSaveInstalled;

		public static Plugin Instance { get; private set; }

		private void Awake()
		{
			Instance = this;
			root = ((BaseUnityPlugin)this).Config.Bind<string>("Storage", "Root", Ledger.DefaultRoot, "Permanent independent storage. Use bm_export before changing; migrate with bm_import after verification.");
			offline = ((BaseUnityPlugin)this).Config.Bind<bool>("Storage", "UseOfflineIdentity", false, "For offline profiles: use a persisted local account UUID in the durable storage root. Keep this setting consistent; never mixes with online account namespaces.");
			supported = typeof(Run).Module.ModuleVersionId.ToString() == "1189835d-0fbe-45e7-8dc9-17f7e9a40d5b";
			Content = new NativeContent();
			Encounters = new EncounterAdapters(this);
			Network = new NetworkBridge(this);
			Run.onRunStartGlobal += RunStarted;
			Run.onRunDestroyGlobal += RunDestroyed;
			Run.onServerGameOver += GameOver;
			RoR2Application.onLoad = (Action)Delegate.Combine(RoR2Application.onLoad, new Action(Loaded));
			LocalUserManager.onUserSignOut += SignedOut;
			LocalUserManager.onUserSignIn += SignedIn;
			SkinPreferences.Install();
			NativeUI.Install();
			if (supported)
			{
				Encounters.Install();
			}
			else
			{
				Warn("Unknown RoR2 module: encounter adapters disabled. Ledger and UI remain available.");
			}
			if (Chainloader.PluginInfos.TryGetValue("com.KingEnderBrine.ProperSave", out var value) && value.Metadata.Version.ToString() == "3.0.7")
			{
				ProperSaveBridge.Install(this);
				properSaveInstalled = true;
			}
		}

		private void Loaded()
		{
			NativeContent.ResolveRoster();
			foreach (LocalUser readOnlyLocalUsers in LocalUserManager.readOnlyLocalUsersList)
			{
				Ledger ledger = GetLedger(readOnlyLocalUsers);
				if (ledger != null)
				{
					Restore(readOnlyLocalUsers, ledger);
				}
			}
			WriteDiagnostics();
		}

		private void SignedOut(LocalUser u)
		{
			profiles.Remove(u);
		}

		private void SignedIn(LocalUser u)
		{
			Ledger ledger = GetLedger(u);
			if (ledger != null)
			{
				Restore(u, ledger);
			}
		}

		internal Ledger GetLedger(LocalUser user)
		{
			if (((user != null) ? user.userProfile : null) == null)
			{
				return null;
			}
			try
			{
				string text = ProfileKeyFor(user);
				if (text == null)
				{
					return null;
				}
				if (!ledgers.TryGetValue(text, out var value))
				{
					value = new Ledger(root.Value, text);
					ledgers.Add(text, value);
					Restore(user, value);
				}
				return value;
			}
			catch (Exception ex)
			{
				Warn("Progress not saved: " + ex.GetType().Name + " " + ex.Message);
				return null;
			}
		}

		internal RunState ViewRun(LocalUser user)
		{
			if (!NetworkServer.active)
			{
				return Network.View(ProfileKeyFor(user));
			}
			return Current;
		}

		internal string ProfileKeyFor(LocalUser user)
		{
			try
			{
				if (((user != null) ? user.userProfile : null) == null || string.IsNullOrWhiteSpace(user.userProfile.fileName))
				{
					return null;
				}
				if (profiles.TryGetValue(user, out var value))
				{
					return value;
				}
				string text = (offline.Value ? OfflineIdentity.LoadOrCreate(root.Value) : AccountIdentity.Resolve());
				if (text == null)
				{
					Status = "Account identity unavailable. Offline play can use Storage.UseOfflineIdentity.";
					return null;
				}
				value = Ledger.ProfileKey(text, user.userProfile.fileName);
				profiles[user] = value;
				return value;
			}
			catch (Exception ex)
			{
				Warn("Profile identity unavailable: " + ex.Message);
				return null;
			}
		}

		internal string Player(NetworkUser user)
		{
			LocalUser val = ((IEnumerable<LocalUser>)LocalUserManager.readOnlyLocalUsersList).FirstOrDefault((Func<LocalUser, bool>)((LocalUser l) => (Object)(object)l.currentNetworkUser == (Object)(object)user));
			if (val != null)
			{
				return ProfileKeyFor(val);
			}
			return Network.PlayerKey(user);
		}

		internal void BindPlayers()
		{
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Invalid comparison between Unknown and I4
			if (Current == null)
			{
				return;
			}
			foreach (NetworkUser readOnlyInstances in NetworkUser.readOnlyInstancesList)
			{
				if (readOnlyInstances.isParticipating && Object.op_Implicit((Object)(object)readOnlyInstances.master) && (int)readOnlyInstances.master.teamIndex == 1)
				{
					string text = Player(readOnlyInstances);
					SurvivorIdentity survivorIdentity = BeyondMasteryAPI.Resolve(readOnlyInstances.GetSurvivorPreference());
					if (text != null && survivorIdentity != null)
					{
						Current.Bind(text, survivorIdentity.Key);
					}
				}
			}
		}

		private void RunStarted(Run run)
		{
			//IL_0022: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Invalid comparison between Unknown and I4
			//IL_0035: Unknown result type (might be due to invalid IL or missing references)
			if (!NetworkServer.active)
			{
				Current = null;
				Status = "Waiting for negotiated host evidence; no guessed awards";
				return;
			}
			Current = new RunState
			{
				Difficulty = (((int)run.selectedDifficulty == 2) ? "Monsoon" : (Enum.GetName(typeof(DifficultyIndex), run.selectedDifficulty) ?? "Unsupported")),
				Mode = ((object)run).GetType().Name,
				Context = RunContext(run)
			};
			BindPlayers();
			Status = (supported ? "Authoritative encounter tracking active" : "Unsupported game build; tracking disabled");
		}

		private static string[] RunContext(Run run)
		{
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_0087: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			List<string> list = Chainloader.PluginInfos.Values.Select((PluginInfo p) => "mod:" + p.Metadata.GUID + "@" + p.Metadata.Version).ToList();
			for (int num = 0; num < ArtifactCatalog.artifactCount; num++)
			{
				ArtifactDef artifactDef = ArtifactCatalog.GetArtifactDef((ArtifactIndex)num);
				if (Object.op_Implicit((Object)(object)artifactDef) && Object.op_Implicit((Object)(object)RunArtifactManager.instance) && RunArtifactManager.instance.IsArtifactEnabled(artifactDef))
				{
					list.Add("artifact:" + artifactDef.cachedName);
				}
			}
			Enumerator<ExpansionDef> enumerator = ExpansionCatalog.expansionDefs.GetEnumerator();
			try
			{
				while (enumerator.MoveNext())
				{
					ExpansionDef current = enumerator.Current;
					if (Object.op_Implicit((Object)(object)current))
					{
						list.Add("expansion:" + ((Object)current).name + "=" + run.IsExpansionEnabled(current));
					}
				}
			}
			finally
			{
				((IDisposable)enumerator/*cast due to .constrained prefix*/).Dispose();
			}
			list.Add("game-module:" + typeof(Run).Module.ModuleVersionId);
			return list.OrderBy((string x) => x).ToArray();
		}

		private void RunDestroyed(Run run)
		{
			Current = null;
			Encounters.Clear();
		}

		private void GameOver(Run run, GameEndingDef ending)
		{
			if (Current == null)
			{
				return;
			}
			foreach (Grant item in Current.Finish(Object.op_Implicit((Object)(object)ending) && ending.isWin))
			{
				Commit(item);
			}
			Network.BroadcastState();
		}

		internal void Resolve(Evidence evidence)
		{
			if (Current == null)
			{
				return;
			}
			foreach (Grant item in Current.Resolve(evidence))
			{
				Commit(item);
			}
			Network.BroadcastState();
		}

		internal bool Commit(Grant grant)
		{
			LocalUser val = ((IEnumerable<LocalUser>)LocalUserManager.readOnlyLocalUsersList).FirstOrDefault((Func<LocalUser, bool>)((LocalUser u) => ProfileKeyFor(u) == grant.Player));
			if (val == null)
			{
				try
				{
					if (NetworkServer.active && Network.Queue(grant))
					{
						return true;
					}
				}
				catch (Exception ex)
				{
					Warn("Remote progress not saved: " + ex.Message);
				}
				if (!pending.Any((Grant g) => g.Player == grant.Player && g.Id == grant.Id && g.Run == grant.Run) && pending.Count < 512)
				{
					pending.Add(grant);
				}
				return false;
			}
			Ledger ledger = GetLedger(val);
			try
			{
				if (ledger == null)
				{
					throw new IOException("Ledger unavailable");
				}
				bool celebrate = ledger.Earn(grant) && ledger.ClaimCelebration(grant.Id);
				Project(val, grant, celebrate);
				BeyondMasteryAPI.Earned(grant.Player, grant.Entitlement);
				return true;
			}
			catch (Exception ex2)
			{
				if (!pending.Any((Grant g) => g.Player == grant.Player && g.Id == grant.Id) && pending.Count < 512)
				{
					pending.Add(grant);
				}
				Warn("Progress not saved; pending retry: " + ex2.Message);
				return false;
			}
		}

		private void Update()
		{
			Network?.Tick();
			if (!(Time.unscaledTime < nextRetry))
			{
				nextRetry = Time.unscaledTime + 10f;
				Grant[] array = pending.ToArray();
				pending.Clear();
				Grant[] array2 = array;
				foreach (Grant grant in array2)
				{
					Commit(grant);
				}
			}
		}

		private void Project(LocalUser user, Grant g, bool celebrate)
		{
			AchievementDef achievementDef = AchievementManager.GetAchievementDef(g.Id);
			UnlockableDef unlockableDef = UnlockableCatalog.GetUnlockableDef(g.Entitlement);
			if (achievementDef == null || !Object.op_Implicit((Object)(object)unlockableDef) || achievementDef.unlockableRewardIdentifier != g.Entitlement || !Content.Unlockables.ContainsKey(g.Entitlement))
			{
				return;
			}
			bool silent = achievementDef.silent;
			try
			{
				achievementDef.silent = !celebrate;
				if (!user.userProfile.HasAchievement(g.Id))
				{
					user.userProfile.AddAchievement(g.Id, false);
				}
				if (!user.userProfile.HasUnlockable(unlockableDef))
				{
					user.userProfile.GrantUnlockable(unlockableDef);
				}
			}
			finally
			{
				achievementDef.silent = silent;
			}
		}

		private void Restore(LocalUser user, Ledger ledger)
		{
			foreach (Grant item in ledger.State.Grants.Values.Where((Grant g) => g.Id.StartsWith("BeyondMastery.achievement.")))
			{
				Project(user, item, celebrate: false);
			}
			SkinPreferences.Reconcile(user, ledger);
		}

		internal void Warn(string message)
		{
			if (!(Status == message))
			{
				Status = message;
				((BaseUnityPlugin)this).Logger.LogWarning((object)message);
			}
		}

		private void WriteDiagnostics()
		{
			((BaseUnityPlugin)this).Logger.LogInfo((object)("Beyond Mastery storage: " + Path.GetFullPath(root.Value)));
			((BaseUnityPlugin)this).Logger.LogInfo((object)("RoR2 MVID: " + typeof(Run).Module.ModuleVersionId));
			foreach (PluginInfo value in Chainloader.PluginInfos.Values)
			{
				((BaseUnityPlugin)this).Logger.LogInfo((object)("Plugin " + value.Metadata.GUID + " " + value.Metadata.Version));
			}
		}

		[ConCommand(/*Could not decode attribute arguments.*/)]
		private static void StatusCommand(ConCommandArgs args)
		{
			Debug.Log((object)(Instance.Status + "\n" + Path.GetFullPath(Instance.root.Value) + "\nProfile: " + Instance.ProfileKeyFor(((ConCommandArgs)(ref args)).GetSenderLocalUser())));
		}

		[ConCommand(/*Could not decode attribute arguments.*/)]
		private static void Export(ConCommandArgs args)
		{
			(Instance.GetLedger(((ConCommandArgs)(ref args)).GetSenderLocalUser()) ?? throw new InvalidOperationException("No profile")).Export(((ConCommandArgs)(ref args)).GetArgString(0));
		}

		[ConCommand(/*Could not decode attribute arguments.*/)]
		private static void Import(ConCommandArgs args)
		{
			Ledger ledger = Instance.GetLedger(((ConCommandArgs)(ref args)).GetSenderLocalUser());
			if (ledger == null)
			{
				throw new InvalidOperationException("No profile");
			}
			ledger.Import(((ConCommandArgs)(ref args)).GetArgString(0));
			Instance.Restore(((ConCommandArgs)(ref args)).GetSenderLocalUser(), ledger);
		}

		[ConCommand(/*Could not decode attribute arguments.*/)]
		private static void ExportPending(ConCommandArgs args)
		{
			string key = Instance.ProfileKeyFor(((ConCommandArgs)(ref args)).GetSenderLocalUser());
			byte[] array = Codec.Encode(Instance.pending.Where((Grant g) => g.Player == key).ToArray());
			using FileStream fileStream = new FileStream(((ConCommandArgs)(ref args)).GetArgString(0), FileMode.CreateNew, FileAccess.Write);
			fileStream.Write(array, 0, array.Length);
			fileStream.Flush(flushToDisk: true);
		}

		[ConCommand(/*Could not decode attribute arguments.*/)]
		private static void Reset(ConCommandArgs args)
		{
			LocalUser senderLocalUser = ((ConCommandArgs)(ref args)).GetSenderLocalUser();
			Ledger ledger = Instance.GetLedger(senderLocalUser);
			if (ledger == null || ((ConCommandArgs)(ref args)).GetArgString(0) != ledger.State.Profile || ((ConCommandArgs)(ref args)).GetArgString(1) != "CONFIRM")
			{
				throw new InvalidOperationException("Use bm_status and confirm the exact profile hash");
			}
			Grant[] array = ledger.State.Grants.Values.ToArray();
			ledger.Reset("RESET " + ledger.State.Profile);
			Grant[] array2 = array;
			foreach (Grant grant in array2)
			{
				if (grant.Id == Catalogue.Achievement(grant.Survivor, grant.Challenge))
				{
					senderLocalUser.userProfile.RevokeAchievement(grant.Id);
					UnlockableDef unlockableDef = UnlockableCatalog.GetUnlockableDef(grant.Entitlement);
					if (Object.op_Implicit((Object)(object)unlockableDef))
					{
						senderLocalUser.userProfile.RevokeUnlockable(unlockableDef);
					}
				}
			}
			senderLocalUser.userProfile.RequestEventualSave();
		}

		private void OnDestroy()
		{
			Run.onRunStartGlobal -= RunStarted;
			Run.onRunDestroyGlobal -= RunDestroyed;
			Run.onServerGameOver -= GameOver;
			RoR2Application.onLoad = (Action)Delegate.Remove(RoR2Application.onLoad, new Action(Loaded));
			LocalUserManager.onUserSignOut -= SignedOut;
			LocalUserManager.onUserSignIn -= SignedIn;
			SkinPreferences.Uninstall();
			Network?.Dispose();
			Encounters?.Dispose();
			Content?.Dispose();
			NativeUI.Uninstall();
			if (properSaveInstalled)
			{
				ProperSaveBridge.Uninstall();
			}
			foreach (Ledger value in ledgers.Values)
			{
				value.Dispose();
			}
			profiles.Clear();
			Instance = null;
		}
	}
	internal static class AccountIdentity
	{
		public static string Resolve()
		{
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_009e: Unknown result type (might be due to invalid IL or missing references)
			Client instance = Client.Instance;
			if (instance != null && instance.SteamId != 0L)
			{
				return "steam:" + instance.SteamId;
			}
			NetworkUser val = LocalUserManager.readOnlyLocalUsersList.Select((LocalUser u) => u.currentNetworkUser).FirstOrDefault((Func<NetworkUser, bool>)delegate(NetworkUser u)
			{
				//IL_0009: Unknown result type (might be due to invalid IL or missing references)
				//IL_000e: Unknown result type (might be due to invalid IL or missing references)
				if (Object.op_Implicit((Object)(object)u))
				{
					NetworkUserId id = u.id;
					return ((NetworkUserId)(ref id)).HasValidValue();
				}
				return false;
			});
			if (Object.op_Implicit((Object)(object)val))
			{
				return "platform:" + val.id.value + ":" + val.id.strValue;
			}
			return null;
		}
	}
	internal static class ProperSaveBridge
	{
		private static Plugin plugin;

		private const string Key = "com.rodanius.beyondmastery.run.v1";

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void Install(Plugin p)
		{
			plugin = p;
			SaveFile.OnGatherSaveData += Gather;
			Loading.OnLoadingEnded += Loaded;
		}

		private static void Gather(Dictionary<string, object> data)
		{
			if (plugin.Current != null && !plugin.Current.Ended)
			{
				data["com.rodanius.beyondmastery.run.v1"] = Convert.ToBase64String(Checkpoints.Save(plugin.Current));
			}
		}

		private static void Loaded(SaveFile file)
		{
			try
			{
				string text = file.TryGetModdedData<string>("com.rodanius.beyondmastery.run.v1");
				plugin.Encounters.Clear();
				plugin.Current = (string.IsNullOrEmpty(text) ? null : Checkpoints.Restore(Convert.FromBase64String(text)));
				plugin.Network.BroadcastState();
			}
			catch (Exception ex)
			{
				plugin.Current = null;
				plugin.Warn("Run checkpoint rejected; permanent grants retained: " + ex.Message);
			}
		}

		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void Uninstall()
		{
			if (!((Object)(object)plugin == (Object)null))
			{
				SaveFile.OnGatherSaveData -= Gather;
				Loading.OnLoadingEnded -= Loaded;
				plugin = null;
			}
		}
	}
	internal static class SkinPreferences
	{
		private static bool applying;

		internal static void Install()
		{
			UserProfile.onLoadoutChangedGlobal += Changed;
		}

		internal static void Uninstall()
		{
			UserProfile.onLoadoutChangedGlobal -= Changed;
		}

		private static void Changed(UserProfile profile)
		{
			//IL_009d: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b8: Unknown result type (might be due to invalid IL or missing references)
			if (applying || (Object)(object)Plugin.Instance == (Object)null)
			{
				return;
			}
			LocalUser val = ((IEnumerable<LocalUser>)LocalUserManager.readOnlyLocalUsersList).FirstOrDefault((Func<LocalUser, bool>)((LocalUser u) => u.userProfile == profile));
			if (val == null)
			{
				return;
			}
			Ledger ledger = Plugin.Instance.GetLedger(val);
			if (ledger == null)
			{
				return;
			}
			try
			{
				foreach (SurvivorIdentity item in BeyondMasteryAPI.Survivors.Where((SurvivorIdentity s) => Object.op_Implicit((Object)(object)s.Definition) && Object.op_Implicit((Object)(object)s.Definition.bodyPrefab)))
				{
					BodyIndex val2 = BodyCatalog.FindBodyIndex(item.Definition.bodyPrefab);
					SkinDef skin = SkinCatalog.GetBodySkinDef(val2, (int)profile.loadout.bodyLoadoutManager.GetSkinIndex(val2));
					string text = BeyondMasteryAPI.Providers.FirstOrDefault((SkinProviderBinding p) => (Object)(object)p.Skin == (Object)(object)skin)?.SkinProviderId ?? "";
					string key = "cosmetic.active." + item.Key;
					if (!ledger.State.Settings.TryGetValue(key, out var value) || value != text)
					{
						ledger.SetSetting(key, text);
						if (text != "")
						{
							ledger.SetSetting("cosmetic.preferred." + item.Key, text);
						}
					}
				}
			}
			catch (Exception ex)
			{
				Plugin.Instance.Warn("Cosmetic preference not saved: " + ex.Message);
			}
		}

		internal static void Reconcile(LocalUser user, Ledger ledger)
		{
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Expected O, but got Unknown
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_0135: Unknown result type (might be due to invalid IL or missing references)
			if (applying)
			{
				return;
			}
			applying = true;
			try
			{
				Loadout val = new Loadout();
				user.userProfile.CopyLoadout(val);
				bool flag = false;
				foreach (SurvivorIdentity item in BeyondMasteryAPI.Survivors.Where((SurvivorIdentity s) => Object.op_Implicit((Object)(object)s.Definition) && Object.op_Implicit((Object)(object)s.Definition.bodyPrefab)))
				{
					if (!ledger.State.Settings.TryGetValue("cosmetic.active." + item.Key, out var provider) || provider == "")
					{
						continue;
					}
					SkinProviderBinding binding = BeyondMasteryAPI.Providers.FirstOrDefault((SkinProviderBinding b) => b.SkinProviderId == provider);
					BodyIndex val2 = BodyCatalog.FindBodyIndex(item.Definition.bodyPrefab);
					if (binding == null)
					{
						val.bodyLoadoutManager.SetSkinIndex(val2, 0u);
						flag = true;
					}
					else if (ledger.State.Grants.Values.Any((Grant g) => g.Entitlement == binding.EntitlementId))
					{
						int num = SkinCatalog.FindLocalSkinIndexForBody(val2, binding.Skin);
						if (num >= 0)
						{
							val.bodyLoadoutManager.SetSkinIndex(val2, (uint)num);
							flag = true;
						}
					}
				}
				if (flag)
				{
					user.userProfile.SetLoadout(val);
				}
			}
			finally
			{
				applying = false;
			}
		}

		internal static void RestorePreference(LocalUser user, SurvivorIdentity survivor)
		{
			Ledger ledger = Plugin.Instance.GetLedger(user);
			if (ledger != null && ledger.State.Settings.TryGetValue("cosmetic.preferred." + survivor.Key, out var provider))
			{
				SkinProviderBinding binding = BeyondMasteryAPI.Providers.FirstOrDefault((SkinProviderBinding b) => b.SkinProviderId == provider);
				if (binding != null && ledger.State.Grants.Values.Any((Grant g) => g.Entitlement == binding.EntitlementId))
				{
					ledger.SetSetting("cosmetic.active." + survivor.Key, provider);
					Reconcile(user, ledger);
				}
			}
		}
	}
}
namespace BeyondMastery.Core
{
	public static class Catalogue
	{
		public const string Rules = "standard.monsoon-karma8.v1";

		public static readonly Challenge[] Challenges = new Challenge[10]
		{
			new Challenge("mithrix_karma8", "Lunar Judgment", "lunar_sovereign", "Complete Mithrix with encounter-applied Karma 8 or higher."),
			new Challenge("voidling", "Beyond the Void", "void_ascendant", "Complete the entire Voidling encounter."),
			new Challenge("false_son", "Fallen Colossus", "gilded_colossus", "Complete False Son at Prime Meridian."),
			new Challenge("twisted_scavenger", "Lunar Exile", "lunar_exile", "Defeat one Twisted Scavenger in A Moment, Whole."),
			new Challenge("solus_wing", "Broken Wing", "alloy_interceptor", "Complete Solus Wing in Solutional Haunt."),
			new Challenge("collective_accept", "Accept and Decompile", "collective_digital", "Resolve the Collective offering through Acceptance."),
			new Challenge("collective_reject", "Purge", "collective_reclaimed", "Complete the hostile Solus Heart encounter and Rejection."),
			new Challenge("anima_harvester", "Memento of Bone", "ossuary", "Complete both Anima Harvester phases and final resolution."),
			new Challenge("echo_of_providence", "Echo of the Bulwark", "providence_effigy", "Complete the entire Echo of Providence encounter."),
			new Challenge("all_major_bosses", "The Final Reckoning", "worldbreaker", "Complete all eight bosses on this survivor in one run, then win. Rejection is required; Acceptance is not.")
		};

		public static readonly string[] Required = (from x in Challenges
			where x.Key != "collective_accept" && x.Key != "all_major_bosses"
			select x.Key).ToArray();

		public static Challenge Find(string key)
		{
			return Challenges.Single((Challenge x) => x.Key == key);
		}

		public static string Achievement(string survivor, string challenge)
		{
			return "BeyondMastery.achievement." + survivor + "." + challenge + ".v1";
		}

		public static string Entitlement(string survivor, string challenge)
		{
			return "BeyondMastery.reward." + survivor + "." + Find(challenge).Family + ".v1";
		}

		public static bool StandardDifficulty(string difficulty)
		{
			if (!(difficulty == "Monsoon"))
			{
				return new string[8] { "Eclipse1", "Eclipse2", "Eclipse3", "Eclipse4", "Eclipse5", "Eclipse6", "Eclipse7", "Eclipse8" }.Contains(difficulty);
			}
			return true;
		}

		public static void ValidateKey(string key)
		{
			if (string.IsNullOrEmpty(key) || key.Length > 160 || key.Any((char c) => !char.IsLetterOrDigit(c) && c != '.' && c != '_' && c != '-'))
			{
				throw new ArgumentException("Invalid stable identity");
			}
		}
	}
	public sealed class Challenge
	{
		public readonly string Key;

		public readonly string Name;

		public readonly string Family;

		public readonly string Requirement;

		public Challenge(string key, string name, string family, string requirement)
		{
			Key = key;
			Name = name;
			Family = family;
			Requirement = requirement;
		}
	}
	[DataContract]
	public sealed class Participant
	{
		[DataMember]
		public string Player;

		[DataMember]
		public string Survivor;
	}
	[DataContract]
	public sealed class Evidence
	{
		[DataMember]
		public string Encounter;

		[DataMember]
		public string Challenge;

		[DataMember]
		public string Scene;

		[DataMember]
		public string Adapter;

		[DataMember]
		public string Resolution;

		[DataMember]
		public int Karma