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