Decompiled source of PLL Library v3.1.0
PLL_Library.dll
Decompiled 2 weeks 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.Collections.ObjectModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.ExceptionServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Serialization.Json; using System.Runtime.Versioning; using System.Security.Cryptography; using System.Text; using System.Threading; using Ashley.MeshSplitter; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using ExitGames.Client.Photon; using HarmonyLib; using Microsoft.CodeAnalysis; using Peak; using Peak.Island; using Peak.Network; using Photon.Pun; using Photon.Realtime; using Steamworks; using TMPro; using Unity.Collections; using Unity.Mathematics; using UnityEngine; using UnityEngine.Audio; using UnityEngine.EventSystems; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.Rendering; using UnityEngine.Rendering.UnifiedRayTracing; using UnityEngine.Rendering.Universal; using UnityEngine.SceneManagement; using UnityEngine.Sprites; using UnityEngine.UI; using Zorro.ControllerSupport; using Zorro.Core; using Zorro.Core.Serizalization; using Zorro.Settings; using Zorro.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("Aeralis Foundation")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("[DirectX 11] Map layout mod and custom segment library.")] [assembly: AssemblyFileVersion("3.1.0.0")] [assembly: AssemblyInformationalVersion("3.1.0+63489a713810c57c94a2190a21039d838f870c9a")] [assembly: AssemblyProduct("AF.PLL.Library")] [assembly: AssemblyTitle("AF.PLL.Library")] [assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly's creation was supported by AI: Code Suggestions, Log Lines (print statements) and Research.")] [assembly: AssemblyMetadata("AI_Model_Vendor", "OpenAI")] [assembly: AssemblyVersion("3.1.0.0")] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace PLL.Library { internal static class DeveloperNoteLoader { private sealed class BoundedTextDownloadHandler : DownloadHandlerScript { private const int ReceiveBufferSize = 4096; private static readonly UTF8Encoding Utf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private readonly byte[] Content; private int Length; internal bool LimitExceeded { get; private set; } internal BoundedTextDownloadHandler(int ByteLimit) : base(new byte[4096]) { Content = new byte[ByteLimit]; } protected override void ReceiveContentLengthHeader(ulong contentLength) { LimitExceeded = contentLength > (ulong)Content.Length; } protected override bool ReceiveData(byte[] data, int dataLength) { if (LimitExceeded || dataLength > Content.Length - Length) { LimitExceeded = true; return false; } if (dataLength == 0) { return true; } Buffer.BlockCopy(data, 0, Content, Length, dataLength); Length += dataLength; return true; } internal bool TryGetText(out string Text) { try { Text = Utf8.GetString(Content, 0, Length); return true; } catch (DecoderFallbackException) { Text = string.Empty; return false; } } } private const string Url = "https://raw.githubusercontent.com/Aeralis-Foundation/Notes/main/Games/Mods/PEAK/PLL/DeveloperNote.md"; private const string FailureText = "No networking or connection failure? Well the same for me... v.v"; private const int ByteLimit = 32768; private const int TimeoutSeconds = 10; private static UnityWebRequestAsyncOperation? Load; internal static string? Result { get; private set; } internal static event Action<string>? ResultAvailable; internal static void Start() { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Expected O, but got Unknown //IL_003e: Expected O, but got Unknown Result = null; BoundedTextDownloadHandler boundedTextDownloadHandler = new BoundedTextDownloadHandler(32768); UnityWebRequest val = new UnityWebRequest("https://raw.githubusercontent.com/Aeralis-Foundation/Notes/main/Games/Mods/PEAK/PLL/DeveloperNote.md", "GET", (DownloadHandler)(object)boundedTextDownloadHandler, (UploadHandler)null) { redirectLimit = 2, timeout = 10 }; UnityWebRequestAsyncOperation val2 = null; try { val2 = val.SendWebRequest(); } catch (UnityException ex) { UnityException ex2 = ex; Publish("No networking or connection failure? Well the same for me... v.v"); Plugin.PLLLog.LogError((object)("PLL Library could not start the Aeralis Foundation developer-note request because " + ((Exception)(object)ex2).Message + ". Check that https://raw.githubusercontent.com/Aeralis-Foundation/Notes/main/Games/Mods/PEAK/PLL/DeveloperNote.md is reachable.")); } finally { if (val2 == null) { val.Dispose(); } } if (val2 != null) { Load = val2; ((AsyncOperation)val2).completed += Complete; } } internal static void Stop() { UnityWebRequestAsyncOperation load = Load; Load = null; if (load == null) { return; } ((AsyncOperation)load).completed -= Complete; UnityWebRequest webRequest = load.webRequest; try { webRequest.Abort(); } finally { webRequest.Dispose(); } } private static void Complete(AsyncOperation Operation) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Invalid comparison between Unknown and I4 UnityWebRequestAsyncOperation load = Load; if (load == null || (object)load != Operation) { return; } ((AsyncOperation)load).completed -= Complete; Load = null; UnityWebRequest webRequest = load.webRequest; try { BoundedTextDownloadHandler boundedTextDownloadHandler = (BoundedTextDownloadHandler)(object)webRequest.downloadHandler; string text; string Text; if (boundedTextDownloadHandler.LimitExceeded) { text = $"the response exceeded {32768} bytes"; } else if ((int)webRequest.result != 1) { text = ((webRequest.responseCode > 0) ? $"GitHub returned HTTP {webRequest.responseCode}" : "raw.githubusercontent.com could not be reached"); } else if (!boundedTextDownloadHandler.TryGetText(out Text)) { text = "the response was not valid UTF-8 text"; } else { if (!string.IsNullOrWhiteSpace(Text)) { Publish(Text); return; } text = "the response was empty"; } Publish("No networking or connection failure? Well the same for me... v.v"); Plugin.PLLLog.LogError((object)("PLL Library could not load the Aeralis Foundation developer note because " + text + ". " + string.Format("Check that {0} is reachable and no larger than {1} bytes.", "https://raw.githubusercontent.com/Aeralis-Foundation/Notes/main/Games/Mods/PEAK/PLL/DeveloperNote.md", 32768))); } finally { webRequest.Dispose(); } } private static void Publish(string Text) { Result = Text; DeveloperNoteLoader.ResultAvailable?.Invoke(Text); } } public static class MyPluginInfo { public const string PLUGIN_GUID = "af.pll.library"; public const string PLUGIN_NAME = "AF.PLL.Library"; public const string PLUGIN_VERSION = "3.1.0"; } public static class PLLLibrary { public const string PluginGuid = "af.pll.library"; public const string PluginName = "AF.PLL.Library"; public const string PluginVersion = "3.1.0"; public const string StartConnector = "af.pll.library/connector/start/v1"; public const string LayoutConnector = "af.pll.library/connector/layout/v1"; public static string? CurrentSegmentId => Map.CurrentSource?.Id; public static string? CurrentSegmentTypeId => Map.CurrentSource?.SegmentTypeId; public static string? CurrentBiomeId => Map.CurrentSource?.BiomeId; public static IReadOnlyList<PLLHookedProp> HookedProps => HookedPropRuntime.HookedProps; public static event Action<PLLHookedProp>? HookedPropAdded { add { HookedPropRuntime.Added += value; } remove { HookedPropRuntime.Added -= value; } } public static event Action<PLLHookedProp>? HookedPropRemoving { add { HookedPropRuntime.Removing += value; } remove { HookedPropRuntime.Removing -= value; } } public static int EmitEffect(string hook) { return AuthoringRuntime.Emit(hook); } public static int StopEffects(string hook) { return AuthoringRuntime.Stop(hook); } public static int EmitStinger(string hook) { return AuthoringRuntime.EmitStinger(hook); } public static int StopStingers(string hook) { return AuthoringRuntime.StopStingers(hook); } public static void DeployLayout(LayoutDefinition definition) { LayoutRegistry.Register(definition); } public static void ModifyLayout(LayoutModification modification) { LayoutRegistry.Register(modification); } public static IReadOnlyList<PLLHookedProp> GetHookedProps(string hook) { return HookedPropRuntime.Get(hook); } internal static void NotifyHookedPropDestroyed(PLLHookedProp value) { HookedPropRuntime.NotifyDestroyed(value); } } [BepInPlugin("af.pll.library", "AF.PLL.Library", "3.1.0")] public sealed class Plugin : BaseUnityPlugin, IInRoomCallbacks, IMatchmakingCallbacks { private Harmony? harmony; private readonly HashSet<IEnumerator> mapRoutines = new HashSet<IEnumerator>(); private bool teardownStarted; private static Plugin instance; public static ManualLogSource PLLLog { get; private set; } internal static Coroutine StartRoutine(IEnumerator routine) { return instance.StartMapRoutine(routine); } private void Awake() { //IL_0048: Unknown result type (might be due to invalid IL or missing references) //IL_0052: Expected O, but got Unknown PLLLog = ((BaseUnityPlugin)this).Logger; try { FileManager.Initialize(((BaseUnityPlugin)this).Info.Metadata); BiomeRegistration.DiscoverStartupBundles(); AuthoringRuntime.Initialize(((Component)this).gameObject); instance = this; SceneManager.sceneUnloaded += OnSceneUnloaded; harmony = new Harmony("af.pll.library"); harmony.PatchAll(typeof(Plugin).Assembly); PhotonNetwork.AddCallbackTarget((object)this); PhotonNetwork.NetworkingClient.StateChanged += OnNetworkStateChanged; DeveloperNoteLoader.Start(); } catch (Exception ex) { ((Behaviour)this).enabled = false; try { Teardown(); } catch (Exception ex2) { throw new AggregateException("PLL Library initialization and rollback both failed.", ex, ex2); } throw; } } private void Start() { try { LayoutRegistry.Seal(); } catch (Exception ex) { ((Behaviour)this).enabled = false; try { Teardown(); } catch (Exception ex2) { throw new AggregateException("PLL Library layout registration and rollback both failed.", ex, ex2); } throw; } } private void Teardown() { if (teardownStarted) { return; } teardownStarted = true; List<Exception> failures = new List<Exception>(); Release(DeveloperNoteLoader.Stop); Release(delegate { ReleaseMapOwnership(Map.ReleaseCurrentRun); }); Release(Map.ClearTransientPreflight); Release(delegate { SceneManager.sceneUnloaded -= OnSceneUnloaded; }); Release(delegate { PhotonNetwork.NetworkingClient.StateChanged -= OnNetworkStateChanged; }); Release(delegate { PhotonNetwork.RemoveCallbackTarget((object)this); }); Release(PllSettingsSurface.Shutdown); Release(MenuVersionMark.Shutdown); Release(MainMenuDeveloperBar.Shutdown); Release(TerminalLayoutSelector.Shutdown); Release(TerminalLayoutSelection.Shutdown); Release(delegate { Harmony? obj = harmony; if (obj != null) { obj.UnpatchSelf(); } }); harmony = null; Release(BiomeRegistration.ClearLiveCatalogs); Release(AuthoringRuntime.Shutdown); if ((Object)(object)instance == (Object)(object)this) { instance = null; } if (failures.Count == 1) { throw failures[0]; } if (failures.Count == 0) { return; } throw new AggregateException("PLL Library teardown failed.", failures); void Release(Action action) { try { action(); } catch (Exception item) { failures.Add(item); } } } private Coroutine StartMapRoutine(IEnumerator routine) { if (routine == null) { throw new ArgumentNullException("routine"); } if (!mapRoutines.Add(routine)) { throw new InvalidOperationException("A map coroutine is already owned by the plugin."); } try { return ((MonoBehaviour)this).StartCoroutine(ObserveMapRoutine(routine)); } catch { mapRoutines.Remove(routine); throw; } } private IEnumerator ObserveMapRoutine(IEnumerator routine) { try { while (routine.MoveNext()) { yield return routine.Current; } } finally { Plugin plugin = this; try { (routine as IDisposable)?.Dispose(); } finally { plugin.mapRoutines.Remove(routine); } } } private void StopMapRoutines() { ((MonoBehaviour)this).StopAllCoroutines(); if (mapRoutines.Count == 0) { return; } List<IEnumerator> list = new List<IEnumerator>(mapRoutines); mapRoutines.Clear(); Exception ex = null; foreach (IEnumerator item in list) { try { (item as IDisposable)?.Dispose(); } catch (Exception ex2) { ex = ((ex == null) ? ex2 : new AggregateException("Multiple map coroutine releases failed.", ex, ex2)); } } if (ex == null) { return; } throw ex; } private void ReleaseMapOwnership(Action release) { List<Exception> failures = new List<Exception>(); Try(release); Try(StopMapRoutines); if (failures.Count == 1) { throw failures[0]; } if (failures.Count != 0) { throw new AggregateException("Map ownership release failed.", failures); } void Try(Action action) { try { action(); } catch (Exception item) { failures.Add(item); } } } private void ReleaseScene(Scene scene) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (Map.OwnsScene(SceneHandle.op_Implicit(((Scene)(ref scene)).handle))) { ReleaseMapOwnership(delegate { //IL_0006: Unknown result type (might be due to invalid IL or missing references) Map.ReleaseScene(SceneHandle.op_Implicit(((Scene)(ref scene)).handle)); }); } else { Map.ReleaseScene(SceneHandle.op_Implicit(((Scene)(ref scene)).handle)); } } private void OnSceneUnloaded(Scene scene) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) if (!teardownStarted) { ReleaseScene(scene); } } private void OnNetworkStateChanged(ClientState previous, ClientState current) { //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) if (!teardownStarted) { Map.CurrentRun?.Network.GenerationBarrier.ConnectionStateChanged(current); QuicksaveRoomGate.ConnectionStateChanged(current); IslandLoadPublication.ConnectionStateChanged(current); FinalManifestConsensus.ConnectionStateChanged(current); } } private void OnDestroy() { Teardown(); } private void Update() { IslandLoadPublication.Tick(); Map.CurrentRun?.Network.DeferredOperations.Tick(); } void IInRoomCallbacks.OnRoomPropertiesUpdate(Hashtable propertiesThatChanged) { if (!teardownStarted) { IslandLoadPublication.RoomPropertiesChanged(propertiesThatChanged); FinalManifestConsensus.MaintainCurrent(); } } void IInRoomCallbacks.OnMasterClientSwitched(Player newMasterClient) { if (!teardownStarted) { Map.CurrentRun?.Network.GenerationBarrier.MasterClientChanged(); IslandLoadPublication.MasterClientChanged(); FinalManifestConsensus.MaintainCurrent(); if (newMasterClient.IsLocal) { TerminalLayoutSelection.Reset(); } else { TerminalLayoutSelection.RefreshAuthority(); } } } void IInRoomCallbacks.OnPlayerEnteredRoom(Player newPlayer) { if (!teardownStarted) { FinalManifestConsensus.MaintainCurrent(); } } void IInRoomCallbacks.OnPlayerLeftRoom(Player otherPlayer) { if (!teardownStarted) { Map.CurrentRun?.Network.ViewBatches.PlayerLeft(otherPlayer); Map.CurrentRun?.Network.GenerationBarrier.PlayerLeft(otherPlayer); FinalManifestConsensus.MaintainCurrent(); } } void IInRoomCallbacks.OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps) { } void IMatchmakingCallbacks.OnFriendListUpdate(List<FriendInfo> friendList) { } void IMatchmakingCallbacks.OnCreatedRoom() { if (!teardownStarted) { TerminalLayoutSelection.Reset(); } } void IMatchmakingCallbacks.OnCreateRoomFailed(short returnCode, string message) { if (!teardownStarted) { QuicksaveRoomGate.MatchmakingFailed("room creation", returnCode, message); } } void IMatchmakingCallbacks.OnJoinedRoom() { if (!teardownStarted && PhotonNetwork.IsMasterClient) { TerminalLayoutSelection.Reset(); } } void IMatchmakingCallbacks.OnJoinRoomFailed(short returnCode, string message) { if (!teardownStarted) { QuicksaveRoomGate.MatchmakingFailed("room join", returnCode, message); } } void IMatchmakingCallbacks.OnJoinRandomFailed(short returnCode, string message) { if (!teardownStarted) { QuicksaveRoomGate.MatchmakingFailed("random room join", returnCode, message); } } void IMatchmakingCallbacks.OnLeftRoom() { if (!teardownStarted) { Map.ClearTransientPreflight(); FinalManifestConsensus.Reset(); AscentUIStartPatch.ResetResolved(); QuicksaveRoomGate.RoomLeft(); TerminalLayoutSelection.Reset(); } } } internal sealed class ConfigDocument { private readonly struct RawSetting { internal string? SettingType { get; } internal string Value { get; } internal RawSetting(string? SettingType, string Value) { this.SettingType = SettingType; this.Value = Value; } } private const string LegacyPublicSchemaRevision = "17"; private const string PreviousPublicSchemaRevision = "20"; private const string CurrentSchemaRevision = "21"; private readonly string Path; private readonly bool Exists; private readonly Dictionary<string, Dictionary<string, RawSetting>> RawSettings = new Dictionary<string, Dictionary<string, RawSetting>>(StringComparer.Ordinal); private string SchemaRevision { get; } private ConfigDocument(string Path, bool Exists, IEnumerable<string> Lines) { this.Path = Path; this.Exists = Exists; Parse(Lines); SchemaRevision = ((!Exists) ? "21" : (TryGet("Version", "SchemaRevision", out var Setting) ? Setting.Value : string.Empty)); } internal static ConfigBindings Open(string Path, BepInPlugin OwnerMetadata) { ConfigDocument configDocument = Read(Path); bool exists = configDocument.Exists; bool flag = exists; if (flag) { bool flag2; switch (configDocument.SchemaRevision) { case "17": case "20": case "21": flag2 = true; break; default: flag2 = false; break; } flag = !flag2; } if (flag) { throw new InvalidDataException("Configuration schema '" + configDocument.SchemaRevision + "' is unsupported; expected '21'."); } ConfigBindings configBindings = ConfigBindings.Bind(Path, OwnerMetadata, "21"); List<BoundConfigSetting> list = configDocument.FindMismatchedSettings(configBindings); bool flag3 = configDocument.SchemaRevision != "21" || list.Count != 0 || !configDocument.SchemaMatches(configBindings); foreach (BoundConfigSetting item in list) { if (item.Migratable) { item.Entry.BoxedValue = item.Entry.DefaultValue; } } if (flag3) { configBindings = configDocument.Replace(configBindings, OwnerMetadata); } else if (!configDocument.Exists) { configBindings.Config.Save(); } configBindings.Config.SaveOnConfigSet = true; return configBindings; } private static ConfigDocument Read(string Path) { bool flag = File.Exists(Path); IEnumerable<string> lines; if (!flag) { IEnumerable<string> enumerable = Array.Empty<string>(); lines = enumerable; } else { lines = File.ReadLines(Path); } return new ConfigDocument(Path, flag, lines); } private void Parse(IEnumerable<string> Lines) { string key = string.Empty; string settingType = null; foreach (string Line in Lines) { string text = Line.Trim(); if (text.StartsWith("[", StringComparison.Ordinal) && text.EndsWith("]", StringComparison.Ordinal)) { key = text.Substring(1, text.Length - 2); settingType = null; continue; } if (text.StartsWith("# Setting type:", StringComparison.Ordinal)) { settingType = text.Substring("# Setting type:".Length).Trim(); continue; } int num = text.IndexOf('='); if (num >= 0 && !text.StartsWith("#", StringComparison.Ordinal)) { string key2 = text.Substring(0, num).Trim(); if (!RawSettings.TryGetValue(key, out Dictionary<string, RawSetting> value)) { value = new Dictionary<string, RawSetting>(StringComparer.Ordinal); RawSettings.Add(key, value); } value[key2] = new RawSetting(settingType, text.Substring(num + 1).Trim()); settingType = null; } } } private List<BoundConfigSetting> FindMismatchedSettings(ConfigBindings Bindings) { List<BoundConfigSetting> list = new List<BoundConfigSetting>(); foreach (BoundConfigSetting setting in Bindings.Settings) { if (TryGet(setting.Entry.Definition.Section, setting.Entry.Definition.Key, out var Setting) && ((Setting.SettingType != null && !(Setting.SettingType == setting.Kind.TypeName())) || !setting.Kind.ValueMatches(Setting.Value))) { list.Add(setting); } } return list; } private bool SchemaMatches(ConfigBindings Bindings) { foreach (KeyValuePair<string, Dictionary<string, RawSetting>> rawSetting in RawSettings) { foreach (string key in rawSetting.Value.Keys) { if (!Bindings.IsKnown(rawSetting.Key, key)) { return false; } } } return true; } private bool TryGet(string Section, string Key, out RawSetting Setting) { if (RawSettings.TryGetValue(Section, out Dictionary<string, RawSetting> value) && value.TryGetValue(Key, out Setting)) { return true; } Setting = default(RawSetting); return false; } private ConfigBindings Replace(ConfigBindings Source, BepInPlugin OwnerMetadata) { Dictionary<ConfigDefinition, object> dictionary = new Dictionary<ConfigDefinition, object>(); foreach (BoundConfigSetting setting in Source.Settings) { if (setting.Migratable) { dictionary.Add(setting.Entry.Definition, setting.Entry.BoxedValue); } } string text = Path + ".new"; if (File.Exists(text)) { File.Delete(text); } try { ConfigBindings configBindings = ConfigBindings.Bind(text, OwnerMetadata, "21"); foreach (BoundConfigSetting setting2 in configBindings.Settings) { if (setting2.Migratable) { setting2.Entry.BoxedValue = dictionary[setting2.Entry.Definition]; } } configBindings.Config.Save(); File.Replace(text, Path, null); } finally { if (File.Exists(text)) { File.Delete(text); } } return ConfigBindings.Bind(Path, OwnerMetadata, "21"); } } internal sealed class ConfigBindings { private const string DefaultSeed = ""; private const int DefaultScale = 8; private readonly List<BoundConfigSetting> BoundSettings = new List<BoundConfigSetting>(); private readonly Dictionary<string, HashSet<string>> KnownSettings = new Dictionary<string, HashSet<string>>(StringComparer.Ordinal); internal ConfigFile Config { get; } internal IReadOnlyList<BoundConfigSetting> Settings => BoundSettings; internal ConfigEntry<string> LayoutOrder { get; } internal ConfigEntry<string> Seed { get; } internal ConfigEntry<bool> ReplaceGenerated { get; } internal ConfigEntry<bool> AdvertiseLayout { get; } internal ConfigEntry<bool> DoOrder { get; } internal ConfigEntry<bool> GenerativeMap { get; } internal ConfigEntry<bool> InfiniteMap { get; } internal ConfigEntry<string> InfiniteMapRepeatable { get; } internal ConfigEntry<string> InfiniteMapChances { get; } internal ConfigEntry<int> InfiniteMapScale { get; } internal ConfigEntry<int> InfiniteMapBeginIndex { get; } internal ConfigEntry<bool> InfiniteMapRemoveOld { get; } internal ConfigEntry<bool> Winnable { get; } internal ConfigEntry<bool> PeakAtEnd { get; } internal ConfigEntry<bool> GemlessNadir { get; } internal ConfigEntry<bool> WaterFollowsBeach { get; } internal ConfigEntry<bool> VoidWaterFollowsNadir { get; } internal ConfigEntry<bool> RelocatePlane { get; } internal ConfigEntry<bool> RelocateStart { get; } internal ConfigEntry<bool> ValidNadir { get; } internal ConfigEntry<bool> LazyGeneration { get; } internal ConfigEntry<bool> Fogless { get; } internal ConfigEntry<bool> NoRisingGhosts { get; } internal ConfigEntry<bool> NoSleepFog { get; } internal ConfigEntry<bool> NoWater { get; } internal ConfigEntry<bool> NoVoidWater { get; } internal ConfigEntry<bool> NoPlane { get; } internal ConfigEntry<bool> NoSkyProps { get; } internal ConfigEntry<bool> RemoveOld { get; } private ConfigBindings(string Path, BepInPlugin OwnerMetadata, string SchemaRevision) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Expected O, but got Unknown //IL_0180: Unknown result type (might be due to invalid IL or missing references) //IL_018a: Expected O, but got Unknown Config = new ConfigFile(Path, false, OwnerMetadata) { SaveOnConfigSet = false }; Bind("Version", "SchemaRevision", SchemaRevision, "Tracks the configuration schema.", Migratable: false); Seed = Bind("Map", "Seed", "", "Empty chooses a new random world seed; any text produces a deterministic seed."); ReplaceGenerated = Bind("Map", "Replace Generated", DefaultValue: true, "Regenerates PEAK's already generated native segment props with the selected seed."); AdvertiseLayout = Bind("PLL", "Advertise Layout", DefaultValue: false, "Shows one configured PLL layout label beneath PEAK's ascent name."); LayoutOrder = Bind("Map", "LayoutOrder", "Beach > Tropics > Roots > Alpine > Mesa > Volcano > Citadel > Nadir", "Defines an ordered list of public native or custom ordinary layout segments. Volcano and Citadel each include their native lower half."); DoOrder = Bind("Map", "DoOrder", DefaultValue: true, "Enables LayoutOrder for the map."); GenerativeMap = Bind("Map", "Generative Map", DefaultValue: false, "Builds one deterministic layout from registered native and custom biome candidates when Layout Order is disabled."); InfiniteMap = Bind("Map", "InfiniteMap", DefaultValue: false, "Enables infinite map generation."); InfiniteMapRepeatable = Bind("Fields", "InfiniteMap: Repeatable", "", "Up to 255 public segment names eligible for repetition, separated by >."); InfiniteMapChances = Bind("Fields", "InfiniteMap: Chances", "", "Up to 255 non-negative weights mapped by position to InfiniteMap: Repeatable, separated by >."); InfiniteMapScale = Bind("Fields", "InfiniteMap: Scale", 8, new ConfigDescription("Requested total public segment count; configuration accepts 1 through 255, current PEAK navigation resolves at most 254 ordinary internal slots, and an online shared Photon room may separately reject a content-dependent generated object batch when it lacks free room ViewIDs.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(1, 255), Array.Empty<object>())); InfiniteMapBeginIndex = Bind("Fields", "InfiniteMap: Begin Index", -1, "-1 inserts at the final eligible public boundary (before a terminal Nadir); other values are clamped zero-based public insertion boundaries."); InfiniteMapRemoveOld = Bind("Fields", "InfiniteMap: Remove Old", DefaultValue: false, "Removes the default layout so only InfiniteMap: Repeatable segments are used."); Winnable = Bind("Behaviors", "Winnable", DefaultValue: true, "Enables the configured final-zone win behavior."); PeakAtEnd = Bind("Behaviors", "Peak at end", DefaultValue: false, "Attaches full Peak after the actual final public ordinary segment; terminal Volcano and Citadel retain contextual Peak."); GemlessNadir = Bind("Behaviors", "Gemless Nadir", DefaultValue: false, "Allows every Nadir occurrence to load without Scout's Honor."); WaterFollowsBeach = Bind("Behaviors", "Water follows Beach", DefaultValue: true, "Activates the water floor when Beach is reached; otherwise enables it beneath the first segment from the start."); VoidWaterFollowsNadir = Bind("Behaviors", "Void Water follows Nadir", DefaultValue: true, "Activates PEAK's Void Water when its Nadir occurrence is reached; otherwise enables it from the start."); RelocatePlane = Bind("Prerequisites", "Relocate Plane", DefaultValue: true, "Moves PEAK's original crashed plane to the first segment's explicit Start port."); RelocateStart = Bind("Prerequisites", "Relocate Start", DefaultValue: true, "Moves PEAK's active four-player spawn group to the first segment's explicit Start port."); ValidNadir = Bind("Prerequisites", "Valid Nadir", DefaultValue: true, "Requires Shore; Tropics or Roots; Alpine or Mesa; and Volcano or Citadel to precede Nadir before it may require Scout's Honor; otherwise Nadir is gemless."); LazyGeneration = Bind("Optimizations", "Lazy Generation", DefaultValue: true, "Defers procedural prop generation until its prerequisite zone is reached."); Fogless = Bind("Optimizations", "Fogless", DefaultValue: false, "Disables the fog sphere and its relocation behavior."); NoRisingGhosts = Bind("Optimizations", "No Rising Ghosts", DefaultValue: false, "Disables Nadir's rising lost-soul fog and Citadel's rising Gloom."); NoSleepFog = Bind("Optimizations", "No Sleep Fog", DefaultValue: false, "Disables Gloom's stationary sleep fog field."); NoWater = Bind("Optimizations", "No Water", DefaultValue: false, "Disables Beach water and all water relocation behavior."); NoVoidWater = Bind("Optimizations", "No Void Water", DefaultValue: false, "Disables PEAK's Nadir-local Void Water surface and WaterZone."); NoPlane = Bind("Optimizations", "No Plane", DefaultValue: false, "Removes the crashed plane and its affiliates except spawn points."); NoSkyProps = Bind("Optimizations", "No Sky Props", DefaultValue: true, "Restricts native raycast-position prop generation to the active map occurrence."); RemoveOld = Bind("Optimizations", "Remove Old", DefaultValue: true, "Destroys map zones older than the immediately previous zone after progression."); } internal static ConfigBindings Bind(string Path, BepInPlugin OwnerMetadata, string SchemaRevision) { return new ConfigBindings(Path, OwnerMetadata, SchemaRevision); } internal bool IsKnown(string Section, string Key) { if (KnownSettings.TryGetValue(Section, out HashSet<string> value)) { return value.Contains(Key); } return false; } private ConfigEntry<string> Bind(string Section, string Key, string DefaultValue, string Description, bool Migratable = true) { return Add<string>(Config.Bind<string>(Section, Key, DefaultValue, Description), ConfigValueKind.String, Migratable); } private ConfigEntry<bool> Bind(string Section, string Key, bool DefaultValue, string Description) { return Add<bool>(Config.Bind<bool>(Section, Key, DefaultValue, Description), ConfigValueKind.Boolean, Migratable: true); } private ConfigEntry<int> Bind(string Section, string Key, int DefaultValue, string Description) { return Add<int>(Config.Bind<int>(Section, Key, DefaultValue, Description), ConfigValueKind.Int32, Migratable: true); } private ConfigEntry<int> Bind(string Section, string Key, int DefaultValue, ConfigDescription Description) { return Add<int>(Config.Bind<int>(Section, Key, DefaultValue, Description), ConfigValueKind.Int32, Migratable: true); } private ConfigEntry<T> Add<T>(ConfigEntry<T> Entry, ConfigValueKind Kind, bool Migratable) { BoundSettings.Add(new BoundConfigSetting((ConfigEntryBase)(object)Entry, Kind, Migratable)); if (!KnownSettings.TryGetValue(((ConfigEntryBase)Entry).Definition.Section, out HashSet<string> value)) { value = new HashSet<string>(StringComparer.Ordinal); KnownSettings.Add(((ConfigEntryBase)Entry).Definition.Section, value); } value.Add(((ConfigEntryBase)Entry).Definition.Key); return Entry; } } internal readonly struct BoundConfigSetting { internal ConfigEntryBase Entry { get; } internal ConfigValueKind Kind { get; } internal bool Migratable { get; } internal BoundConfigSetting(ConfigEntryBase Entry, ConfigValueKind Kind, bool Migratable) { this.Entry = Entry; this.Kind = Kind; this.Migratable = Migratable; } } internal enum ConfigValueKind { String, Boolean, Int32 } internal static class ConfigValueKindOperations { internal static string TypeName(this ConfigValueKind Kind) { return Kind switch { ConfigValueKind.String => typeof(string).Name, ConfigValueKind.Boolean => typeof(bool).Name, ConfigValueKind.Int32 => typeof(int).Name, _ => throw new ArgumentOutOfRangeException("Kind", Kind, null), }; } internal static bool ValueMatches(this ConfigValueKind Kind, string Value) { bool result; int result2; return Kind switch { ConfigValueKind.String => true, ConfigValueKind.Boolean => bool.TryParse(Value, out result), ConfigValueKind.Int32 => int.TryParse(Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result2), _ => throw new ArgumentOutOfRangeException("Kind", Kind, null), }; } } internal sealed class CustomBiomeConfig { internal BiomeId BiomeId { get; } internal string ConfigPath { get; } internal ConfigEntry<string> Favorites { get; } internal ConfigEntry<string> Overwrites { get; } internal ConfigEntry<string> Before { get; } internal ConfigEntry<string> After { get; } internal ConfigEntry<int> SpawnChance { get; } internal ConfigFile File { get; } internal CustomBiomeConfig(BiomeId biomeId, string configPath, ConfigFile file, ConfigEntry<string> favorites, ConfigEntry<string> overwrites, ConfigEntry<string> before, ConfigEntry<string> after, ConfigEntry<int> spawnChance) { BiomeId = biomeId; ConfigPath = configPath; File = file; Favorites = favorites; Overwrites = overwrites; Before = before; After = after; SpawnChance = spawnChance; } } internal readonly struct CustomBiomeConfigDefaults { internal BiomeId Id { get; } internal BiomeId[] Favorites { get; } internal BiomeId[] Overwrites { get; } internal BiomeId[] Before { get; } internal BiomeId[] After { get; } internal int SpawnChance { get; } private CustomBiomeConfigDefaults(BiomeId id, BiomeId[] favorites, BiomeId[] overwrites, BiomeId[] before, BiomeId[] after, int spawnChance) { Id = id; Favorites = favorites; Overwrites = overwrites; Before = before; After = after; SpawnChance = spawnChance; } internal static CustomBiomeConfigDefaults Create(PllBiomeDefinition definition) { if (!Object.op_Implicit((Object)(object)definition)) { throw new ArgumentNullException("definition"); } BiomeId biomeId = definition.BiomeId; int spawnChance = definition.SpawnChance; if (spawnChance < 0 || spawnChance > 100) { throw new ArgumentOutOfRangeException("definition", spawnChance, $"Biome '{biomeId}' SpawnChance must be between 0 and 100."); } return new CustomBiomeConfigDefaults(biomeId, CopyIds(definition.Favorites, biomeId, "Favorites"), CopyIds(definition.Overwrites, biomeId, "Overwrites", rejectReservedNonLayout: true), CopyIds(definition.Before, biomeId, "Before"), CopyIds(definition.After, biomeId, "After"), spawnChance); } private static BiomeId[] CopyIds(IReadOnlyList<BiomeId> source, BiomeId owner, string name, bool rejectReservedNonLayout = false) { BiomeId[] array = new BiomeId[source.Count]; for (int i = 0; i < array.Length; i++) { BiomeId biomeId = source[i]; if (!biomeId.IsValid || biomeId == owner) { throw new ArgumentException($"Biome '{owner}' contains an invalid {name} entry.", "source"); } if (rejectReservedNonLayout && BiomeLayoutConfigSnapshot.IsReservedNonLayoutBiome(biomeId)) { throw new ArgumentException($"Biome '{owner}' cannot overwrite reserved non-layout biome '{biomeId}'.", "source"); } array[i] = biomeId; } Array.Sort(array, CompareIds); for (int j = 1; j < array.Length; j++) { if (array[j - 1] == array[j]) { throw new ArgumentException($"Biome '{owner}' contains duplicate {name} entry '{array[j]}'.", "source"); } } return array; } private static int CompareIds(BiomeId left, BiomeId right) { return StringComparer.Ordinal.Compare(left.Value, right.Value); } } internal sealed class BiomeLayoutConfigValues { internal BiomeId Id { get; } internal BiomeId[] Favorites { get; } internal BiomeId[] Overwrites { get; } internal BiomeId[] Before { get; } internal BiomeId[] After { get; } internal int SpawnChance { get; } internal BiomeLayoutConfigValues(BiomeId id, BiomeId[] favorites, BiomeId[] overwrites, BiomeId[] before, BiomeId[] after, int spawnChance) { Id = id; Favorites = favorites; Overwrites = overwrites; Before = before; After = after; SpawnChance = spawnChance; } internal bool Favors(BiomeId id) { return Contains(Favorites, id); } internal bool OverwritesBiome(BiomeId id) { return Contains(Overwrites, id); } internal bool PrefersBefore(BiomeId id) { return Contains(Before, id); } internal bool PrefersAfter(BiomeId id) { return Contains(After, id); } private static bool Contains(IReadOnlyList<BiomeId> values, BiomeId id) { for (int i = 0; i < values.Count; i++) { if (values[i] == id) { return true; } } return false; } } internal sealed class BiomeLayoutConfigSnapshot { internal static readonly BiomeLayoutConfigSnapshot Empty = new BiomeLayoutConfigSnapshot(Array.Empty<BiomeLayoutConfigValues>()); private readonly Dictionary<BiomeId, BiomeLayoutConfigValues> byId; internal IReadOnlyList<BiomeLayoutConfigValues> Values { get; } internal string Serialized { get; } internal BiomeLayoutConfigSnapshot(BiomeLayoutConfigValues[] values) { Values = values; byId = new Dictionary<BiomeId, BiomeLayoutConfigValues>(values.Length); foreach (BiomeLayoutConfigValues biomeLayoutConfigValues in values) { byId.Add(biomeLayoutConfigValues.Id, biomeLayoutConfigValues); } Serialized = Serialize(values); } internal bool TryGet(BiomeId id, out BiomeLayoutConfigValues values) { return byId.TryGetValue(id, out values); } private static string Serialize(IReadOnlyList<BiomeLayoutConfigValues> values) { StringBuilder stringBuilder = new StringBuilder(); LengthPrefixedText.Append(stringBuilder, values.Count.ToString(CultureInfo.InvariantCulture)); foreach (BiomeLayoutConfigValues value in values) { LengthPrefixedText.Append(stringBuilder, value.Id.Value); LengthPrefixedText.Append(stringBuilder, FormatIds(value.Favorites)); LengthPrefixedText.Append(stringBuilder, FormatIds(value.Overwrites)); LengthPrefixedText.Append(stringBuilder, FormatIds(value.Before)); LengthPrefixedText.Append(stringBuilder, FormatIds(value.After)); LengthPrefixedText.Append(stringBuilder, value.SpawnChance.ToString(CultureInfo.InvariantCulture)); } return stringBuilder.ToString(); } internal static bool TryDeserialize(string data, IReadOnlyList<CustomBiomeConfig> expected, out BiomeLayoutConfigSnapshot snapshot) { snapshot = Empty; int Offset = 0; if (!LengthPrefixedText.TryRead(data, ref Offset, out string Value) || !int.TryParse(Value, NumberStyles.None, CultureInfo.InvariantCulture, out var result) || result != expected.Count) { return false; } BiomeLayoutConfigValues[] array = new BiomeLayoutConfigValues[result]; for (int i = 0; i < array.Length; i++) { BiomeId biomeId = expected[i].BiomeId; if (!LengthPrefixedText.TryRead(data, ref Offset, out string Value2) || !string.Equals(Value2, biomeId.Value, StringComparison.Ordinal) || !LengthPrefixedText.TryRead(data, ref Offset, out string Value3) || !TryParseCanonicalIds(Value3, biomeId, out BiomeId[] result2) || !LengthPrefixedText.TryRead(data, ref Offset, out string Value4) || !TryParseCanonicalIds(Value4, biomeId, out BiomeId[] result3, rejectReservedNonLayout: true) || !LengthPrefixedText.TryRead(data, ref Offset, out string Value5) || !TryParseCanonicalIds(Value5, biomeId, out BiomeId[] result4) || !LengthPrefixedText.TryRead(data, ref Offset, out string Value6) || !TryParseCanonicalIds(Value6, biomeId, out BiomeId[] result5) || !LengthPrefixedText.TryRead(data, ref Offset, out string Value7) || !int.TryParse(Value7, NumberStyles.None, CultureInfo.InvariantCulture, out var result6) || result6 < 0 || result6 > 100) { return false; } array[i] = new BiomeLayoutConfigValues(biomeId, result2, result3, result4, result5, result6); } if (Offset != data.Length) { return false; } snapshot = new BiomeLayoutConfigSnapshot(array); return string.Equals(snapshot.Serialized, data, StringComparison.Ordinal); } internal static string FormatIds(IReadOnlyList<BiomeId> values) { if (values.Count == 0) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(values.Count * 16); for (int i = 0; i < values.Count; i++) { if (i != 0) { stringBuilder.Append('>'); } stringBuilder.Append(values[i].Value); } return stringBuilder.ToString(); } internal static BiomeId[] ParseConfiguredIds(string? value, BiomeId owner, bool rejectReservedNonLayout = false) { if (string.IsNullOrEmpty(value)) { return Array.Empty<BiomeId>(); } List<BiomeId> list = new List<BiomeId>(); HashSet<BiomeId> hashSet = new HashSet<BiomeId>(); string[] array = value.Split(new char[1] { '>' }, StringSplitOptions.RemoveEmptyEntries); foreach (string text in array) { if (BiomeId.TryParse(text.Trim(), out var id) && id != owner) { if (rejectReservedNonLayout && IsReservedNonLayoutBiome(id)) { throw new InvalidOperationException($"Biome '{owner}' cannot overwrite reserved non-layout biome '{id}'."); } if (hashSet.Add(id)) { list.Add(id); } } } list.Sort(CompareIds); return list.ToArray(); } internal static bool IsReservedNonLayoutBiome(BiomeId id) { if (!(id == NativeBiomeIds.Peak) && !(id == NativeBiomeIds.Caldera)) { return id == NativeBiomeIds.Gloom; } return true; } private static bool TryParseCanonicalIds(string value, BiomeId owner, out BiomeId[] result, bool rejectReservedNonLayout = false) { result = Array.Empty<BiomeId>(); if (value.Length == 0) { return true; } string[] array = value.Split('>'); result = new BiomeId[array.Length]; for (int i = 0; i < array.Length; i++) { if (!BiomeId.TryParse(array[i], out var id) || id == owner || (rejectReservedNonLayout && IsReservedNonLayoutBiome(id)) || (i != 0 && StringComparer.Ordinal.Compare(result[i - 1].Value, id.Value) >= 0)) { result = Array.Empty<BiomeId>(); return false; } result[i] = id; } return true; } private static int CompareIds(BiomeId left, BiomeId right) { return StringComparer.Ordinal.Compare(left.Value, right.Value); } } internal sealed class CustomBiomeConfigBatch { internal BiomeId[] Ids { get; } internal bool Registered { get; private set; } = true; internal CustomBiomeConfigBatch(BiomeId[] ids) { Ids = ids; } internal void CompleteUnregister() { Registered = false; } } internal static class CustomBiomeConfigRegistry { private const string Section = "Generative Map"; private const int ReadableStemLength = 48; private static readonly Dictionary<BiomeId, CustomBiomeConfigDefaults> Pending = new Dictionary<BiomeId, CustomBiomeConfigDefaults>(); private static readonly Dictionary<BiomeId, CustomBiomeConfig> Configurations = new Dictionary<BiomeId, CustomBiomeConfig>(); private static readonly UTF8Encoding StrictUtf8 = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false, throwOnInvalidBytes: true); private static BepInPlugin? ownerMetadata; private static string? directory; private static CustomBiomeConfig[]? startupConfigurations; private static bool registrySealed; internal static void Initialize(BepInPlugin metadata, string configDirectory) { if (metadata == null) { throw new ArgumentNullException("metadata"); } if (configDirectory == null) { throw new ArgumentNullException("configDirectory"); } string fullPath = Path.GetFullPath(configDirectory); if (ownerMetadata != null) { if (ownerMetadata != metadata || !string.Equals(directory, fullPath, StringComparison.Ordinal)) { throw new InvalidOperationException("The custom biome configuration registry is already initialized."); } } else { ownerMetadata = metadata; directory = fullPath; } } internal static CustomBiomeConfigBatch RegisterDefaults(IReadOnlyList<CustomBiomeConfigDefaults> defaults) { if (defaults == null) { throw new ArgumentNullException("defaults"); } if (startupConfigurations != null) { throw new InvalidOperationException("The startup custom biome configuration registry is sealed."); } HashSet<BiomeId> hashSet = new HashSet<BiomeId>(); BiomeId[] array = new BiomeId[defaults.Count]; for (int i = 0; i < defaults.Count; i++) { BiomeId id = defaults[i].Id; if (!id.IsValid || !hashSet.Add(id) || Pending.ContainsKey(id) || Configurations.ContainsKey(id)) { throw new InvalidOperationException($"Biome '{id}' already has a custom configuration."); } array[i] = id; } foreach (CustomBiomeConfigDefaults @default in defaults) { Pending.Add(@default.Id, @default); } return new CustomBiomeConfigBatch(array); } internal static void Unregister(CustomBiomeConfigBatch batch) { if (batch == null) { throw new ArgumentNullException("batch"); } if (registrySealed) { throw new InvalidOperationException("The custom biome configuration registry is sealed."); } if (!batch.Registered) { throw new InvalidOperationException("The custom biome configuration batch is already unregistered."); } BiomeId[] ids = batch.Ids; foreach (BiomeId biomeId in ids) { if (!Pending.ContainsKey(biomeId) && !Configurations.ContainsKey(biomeId)) { throw new InvalidOperationException($"Biome '{biomeId}' has no custom configuration owned by the batch."); } } BiomeId[] ids2 = batch.Ids; foreach (BiomeId key in ids2) { Pending.Remove(key); Configurations.Remove(key); } batch.CompleteUnregister(); } internal static void MaterializePending() { if (Pending.Count == 0) { return; } List<CustomBiomeConfigDefaults> list = new List<CustomBiomeConfigDefaults>(Pending.Values); list.Sort((CustomBiomeConfigDefaults left, CustomBiomeConfigDefaults right) => StringComparer.Ordinal.Compare(left.Id.Value, right.Id.Value)); List<string> createdFiles; CustomBiomeConfig[] array = Prepare(list, out createdFiles); int num = 0; try { for (; num < array.Length; num++) { Configurations.Add(array[num].BiomeId, array[num]); } CustomBiomeConfig[] array2 = array; foreach (CustomBiomeConfig customBiomeConfig in array2) { customBiomeConfig.File.SaveOnConfigSet = true; } foreach (CustomBiomeConfigDefaults item in list) { Pending.Remove(item.Id); } } catch (Exception failure) { for (int num3 = 0; num3 < num; num3++) { Configurations.Remove(array[num3].BiomeId); } DeleteFiles(createdFiles, failure); throw; } } internal static void SealStartup() { if (startupConfigurations == null) { MaterializePending(); startupConfigurations = SortedConfigurations().ToArray(); } } internal static void SealRegistry() { registrySealed = true; } internal static BiomeLayoutConfigSnapshot Capture() { MaterializePending(); CustomBiomeConfig[] array = startupConfigurations; List<CustomBiomeConfig> list = ((array != null) ? new List<CustomBiomeConfig>(array) : SortedConfigurations()); BiomeLayoutConfigValues[] array2 = new BiomeLayoutConfigValues[list.Count]; for (int i = 0; i < array2.Length; i++) { CustomBiomeConfig customBiomeConfig = list[i]; BiomeId biomeId = customBiomeConfig.BiomeId; array2[i] = new BiomeLayoutConfigValues(biomeId, BiomeLayoutConfigSnapshot.ParseConfiguredIds(customBiomeConfig.Favorites.Value, biomeId), BiomeLayoutConfigSnapshot.ParseConfiguredIds(customBiomeConfig.Overwrites.Value, biomeId, rejectReservedNonLayout: true), BiomeLayoutConfigSnapshot.ParseConfiguredIds(customBiomeConfig.Before.Value, biomeId), BiomeLayoutConfigSnapshot.ParseConfiguredIds(customBiomeConfig.After.Value, biomeId), Math.Max(0, Math.Min(100, customBiomeConfig.SpawnChance.Value))); } return new BiomeLayoutConfigSnapshot(array2); } internal static BiomeLayoutConfigSnapshot Active(BiomeLayoutConfigSnapshot snapshot) { if (snapshot == null) { throw new ArgumentNullException("snapshot"); } List<BiomeLayoutConfigValues> list = new List<BiomeLayoutConfigValues>(); foreach (BiomeLayoutConfigValues value in snapshot.Values) { if (Configurations.ContainsKey(value.Id)) { list.Add(value); } } if (list.Count != snapshot.Values.Count) { return new BiomeLayoutConfigSnapshot(list.ToArray()); } return snapshot; } internal static bool TryParseSnapshot(string data, out BiomeLayoutConfigSnapshot snapshot) { if (data == null) { snapshot = BiomeLayoutConfigSnapshot.Empty; return false; } MaterializePending(); CustomBiomeConfig[] array = startupConfigurations; IReadOnlyList<CustomBiomeConfig> readOnlyList2; if (array == null) { IReadOnlyList<CustomBiomeConfig> readOnlyList = SortedConfigurations(); readOnlyList2 = readOnlyList; } else { IReadOnlyList<CustomBiomeConfig> readOnlyList = array; readOnlyList2 = readOnlyList; } IReadOnlyList<CustomBiomeConfig> expected = readOnlyList2; return BiomeLayoutConfigSnapshot.TryDeserialize(data, expected, out snapshot); } private static List<CustomBiomeConfig> SortedConfigurations() { List<CustomBiomeConfig> list = new List<CustomBiomeConfig>(Configurations.Values); list.Sort((CustomBiomeConfig left, CustomBiomeConfig right) => StringComparer.Ordinal.Compare(left.BiomeId.Value, right.BiomeId.Value)); return list; } private static CustomBiomeConfig[] Prepare(IReadOnlyList<CustomBiomeConfigDefaults> defaults, out List<string> createdFiles) { BepInPlugin metadata = ownerMetadata ?? throw new InvalidOperationException("The custom biome configuration registry is not initialized."); string text = directory; HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (CustomBiomeConfig value in Configurations.Values) { hashSet.Add(value.ConfigPath); } string[] array = new string[defaults.Count]; for (int i = 0; i < defaults.Count; i++) { BiomeId id = defaults[i].Id; string text2 = Path.Combine(text, FileName(id)); if (!hashSet.Add(text2)) { throw new InvalidOperationException($"Biome '{id}' collides with another custom configuration path."); } array[i] = text2; } Directory.CreateDirectory(text); createdFiles = new List<string>(); CustomBiomeConfig[] array2 = new CustomBiomeConfig[defaults.Count]; try { for (int j = 0; j < array2.Length; j++) { bool flag = File.Exists(array[j]); array2[j] = Bind(array[j], metadata, defaults[j]); if (!flag) { createdFiles.Add(array[j]); array2[j].File.Save(); } } return array2; } catch (Exception failure) { DeleteFiles(createdFiles, failure); throw; } } private static void DeleteFiles(IReadOnlyList<string> paths, Exception failure) { List<Exception> list = null; foreach (string path in paths) { try { File.Delete(path); } catch (Exception item) { (list ?? (list = new List<Exception>())).Add(item); } } if (list == null) { return; } list.Insert(0, failure); throw new AggregateException("Custom biome configuration transaction and its rollback both failed.", list); } private static CustomBiomeConfig Bind(string path, BepInPlugin metadata, CustomBiomeConfigDefaults defaults) { //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Unknown result type (might be due to invalid IL or missing references) //IL_0010: Expected O, but got Unknown //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00c7: Expected O, but got Unknown ConfigFile val = new ConfigFile(path, false, metadata) { SaveOnConfigSet = false }; ConfigEntry<string> favorites = val.Bind<string>("Generative Map", "Favorites", FormatDefaults(defaults.Favorites), "Biome IDs preferred next to this biome, separated by >."); ConfigEntry<string> overwrites = val.Bind<string>("Generative Map", "Overwrites", FormatDefaults(defaults.Overwrites), "Biome IDs this biome may replace, separated by >."); ConfigEntry<string> before = val.Bind<string>("Generative Map", "Before", FormatDefaults(defaults.Before), "Biome IDs this biome prefers to precede, separated by >."); ConfigEntry<string> after = val.Bind<string>("Generative Map", "After", FormatDefaults(defaults.After), "Biome IDs this biome prefers to follow, separated by >."); ConfigEntry<int> spawnChance = val.Bind<int>("Generative Map", "Spawn Chance", defaults.SpawnChance, new ConfigDescription("Chance from 0 to 100 that Generative Map includes this biome.", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), Array.Empty<object>())); return new CustomBiomeConfig(defaults.Id, path, val, favorites, overwrites, before, after, spawnChance); } private static string FormatDefaults(IReadOnlyList<BiomeId> values) { if (values.Count == 0) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(values.Count * 18); for (int i = 0; i < values.Count; i++) { if (i != 0) { stringBuilder.Append(" > "); } stringBuilder.Append(values[i].Value); } return stringBuilder.ToString(); } private static string FileName(BiomeId id) { StringBuilder stringBuilder = new StringBuilder(Math.Min(id.Value.Length, 48)); bool flag = false; string value = id.Value; foreach (char c in value) { bool flag2; switch (c) { case '-': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case '_': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': flag2 = true; break; default: flag2 = false; break; } if (flag2) { if (stringBuilder.Length >= 48) { break; } stringBuilder.Append(c); flag = false; } else if (!flag && stringBuilder.Length != 0 && stringBuilder.Length < 48) { stringBuilder.Append('-'); flag = true; } } while (stringBuilder.Length != 0 && stringBuilder[stringBuilder.Length - 1] == '-') { stringBuilder.Length--; } if (stringBuilder.Length == 0) { stringBuilder.Append("biome"); } using SHA256 sHA = SHA256.Create(); string arg = BitConverter.ToString(sHA.ComputeHash(StrictUtf8.GetBytes(id.Value))).Replace("-", string.Empty); return $"{stringBuilder}-{arg}.cfg"; } } public static class FileManager { internal const string DefaultLayoutOrder = "Beach > Tropics > Roots > Alpine > Mesa > Volcano > Citadel > Nadir"; internal const string DefaultRepeatable = ""; internal const string DefaultChances = ""; public static ConfigEntry<string> LayoutOrder { get; private set; } public static ConfigEntry<string> Seed { get; private set; } public static ConfigEntry<bool> ReplaceGenerated { get; private set; } public static ConfigEntry<bool> AdvertiseLayout { get; private set; } public static ConfigEntry<bool> DoOrder { get; private set; } public static ConfigEntry<bool> GenerativeMap { get; private set; } public static ConfigEntry<bool> InfiniteMap { get; private set; } public static ConfigEntry<string> InfiniteMapRepeatable { get; private set; } public static ConfigEntry<string> InfiniteMapChances { get; private set; } public static ConfigEntry<int> InfiniteMapScale { get; private set; } public static ConfigEntry<int> InfiniteMapBeginIndex { get; private set; } public static ConfigEntry<bool> InfiniteMapRemoveOld { get; private set; } public static ConfigEntry<bool> Winnable { get; private set; } public static ConfigEntry<bool> PeakAtEnd { get; private set; } public static ConfigEntry<bool> GemlessNadir { get; private set; } public static ConfigEntry<bool> WaterFollowsBeach { get; private set; } public static ConfigEntry<bool> VoidWaterFollowsNadir { get; private set; } public static ConfigEntry<bool> RelocatePlane { get; private set; } public static ConfigEntry<bool> RelocateStart { get; private set; } public static ConfigEntry<bool> ValidNadir { get; private set; } public static ConfigEntry<bool> LazyGeneration { get; private set; } public static ConfigEntry<bool> Fogless { get; private set; } public static ConfigEntry<bool> NoRisingGhosts { get; private set; } public static ConfigEntry<bool> NoSleepFog { get; private set; } public static ConfigEntry<bool> NoWater { get; private set; } public static ConfigEntry<bool> NoVoidWater { get; private set; } public static ConfigEntry<bool> NoPlane { get; private set; } public static ConfigEntry<bool> NoSkyProps { get; private set; } public static ConfigEntry<bool> RemoveOld { get; private set; } internal static void Initialize(BepInPlugin OwnerMetadata) { string text = Path.Combine(Paths.ConfigPath, "AF", "PLL_Library"); Directory.CreateDirectory(text); CustomBiomeConfigRegistry.Initialize(OwnerMetadata, Path.Combine(text, "Custom Segments")); ConfigBindings configBindings = ConfigDocument.Open(Path.Combine(text, "config.cfg"), OwnerMetadata); LayoutOrder = configBindings.LayoutOrder; Seed = configBindings.Seed; ReplaceGenerated = configBindings.ReplaceGenerated; AdvertiseLayout = configBindings.AdvertiseLayout; DoOrder = configBindings.DoOrder; GenerativeMap = configBindings.GenerativeMap; InfiniteMap = configBindings.InfiniteMap; InfiniteMapRepeatable = configBindings.InfiniteMapRepeatable; InfiniteMapChances = configBindings.InfiniteMapChances; InfiniteMapScale = configBindings.InfiniteMapScale; InfiniteMapBeginIndex = configBindings.InfiniteMapBeginIndex; InfiniteMapRemoveOld = configBindings.InfiniteMapRemoveOld; Winnable = configBindings.Winnable; PeakAtEnd = configBindings.PeakAtEnd; GemlessNadir = configBindings.GemlessNadir; WaterFollowsBeach = configBindings.WaterFollowsBeach; VoidWaterFollowsNadir = configBindings.VoidWaterFollowsNadir; RelocatePlane = configBindings.RelocatePlane; RelocateStart = configBindings.RelocateStart; ValidNadir = configBindings.ValidNadir; LazyGeneration = configBindings.LazyGeneration; Fogless = configBindings.Fogless; NoRisingGhosts = configBindings.NoRisingGhosts; NoSleepFog = configBindings.NoSleepFog; NoWater = configBindings.NoWater; NoVoidWater = configBindings.NoVoidWater; NoPlane = configBindings.NoPlane; NoSkyProps = configBindings.NoSkyProps; RemoveOld = configBindings.RemoveOld; } } internal static class AirportConnectionTransport { [HarmonyPatch] private static class AirportLoadPatch { private static IEnumerable<MethodBase> TargetMethods() { yield return HandleMessage; yield return OnLobbyCreated; } [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> Instructions, MethodBase __originalMethod) { List<CodeInstruction> list = new List<CodeInstruction>(Instructions); int num = 0; int num2 = 0; foreach (CodeInstruction item in list) { if (CodeInstructionExtensions.Calls(item, LoadSceneProcess)) { item.opcode = OpCodes.Call; item.operand = WrapLoadSceneProcess; num++; } else if (CodeInstructionExtensions.Calls(item, Load)) { num2++; } } if (num != 1 || num2 != 1) { throw new MissingMethodException(__originalMethod.DeclaringType?.FullName + "." + __originalMethod.Name + " contained " + $"{num} Airport scene process calls and {num2} loading calls."); } return list; } } private static readonly MethodInfo HandleMessage = AccessTools.DeclaredMethod(typeof(SteamLobbyHandler), "HandleMessage", new Type[3] { typeof(MessageType), typeof(BinaryDeserializer), typeof(CSteamID) }, (Type[])null) ?? throw new MissingMethodException(typeof(SteamLobbyHandler).FullName, "HandleMessage"); private static readonly MethodInfo OnLobbyCreated = AccessTools.DeclaredMethod(typeof(SteamLobbyHandler), "OnLobbyCreated", new Type[1] { typeof(LobbyCreated_t) }, (Type[])null) ?? throw new MissingMethodException(typeof(SteamLobbyHandler).FullName, "OnLobbyCreated"); private static readonly MethodInfo LoadSceneProcess = AccessTools.DeclaredMethod(typeof(LoadingScreenHandler), "LoadSceneProcess", new Type[4] { typeof(string), typeof(bool), typeof(bool), typeof(float) }, (Type[])null) ?? throw new MissingMethodException(typeof(LoadingScreenHandler).FullName, "LoadSceneProcess"); private static readonly MethodInfo Load = AccessTools.DeclaredMethod(typeof(LoadingScreenHandler), "Load", new Type[3] { typeof(LoadingScreenType), typeof(Action), typeof(IEnumerator[]) }, (Type[])null) ?? throw new MissingMethodException(typeof(LoadingScreenHandler).FullName, "Load"); private static readonly MethodInfo WrapLoadSceneProcess = new Func<LoadingScreenHandler, string, bool, bool, float, IEnumerator>(Wrap).Method; private static IEnumerator Wrap(LoadingScreenHandler Handler, string SceneName, bool Networked, bool YieldForCharacterSpawn, float ExtraYieldTimeOnEnd) { return SuspendedPhotonTransport.SendOutgoingAtYields(Handler.LoadSceneProcess(SceneName, Networked, YieldForCharacterSpawn, ExtraYieldTimeOnEnd), null); } } internal sealed class FinalManifestConfirmation { private readonly int[] Actors; internal Room Room { get; } internal ResolvedRunConfiguration Configuration { get; } internal string Revision { get; } internal int LocalActor { get; } internal FinalManifestConfirmation(Room Room, ResolvedRunConfiguration Configuration, string Revision, int LocalActor, int[] Actors) { this.Room = Room; this.Configuration = Configuration; this.Revision = Revision; this.LocalActor = LocalActor; this.Actors = (int[])Actors.Clone(); } internal int[] SnapshotActors() { return (int[])Actors.Clone(); } } internal static class FinalManifestConsensus { private abstract class Round { internal ResolvedRunConfiguration Configuration { get; } internal string Revision { get; } internal string Epoch => Configuration.Protocol.GenerationEpoch; protected Round(ResolvedRunConfiguration Configuration, string Revision) { this.Configuration = Configuration; this.Revision = Revision; } } private sealed class OfflineRound : Round { internal Room? Room { get; } internal OfflineRound(Room? Room, ResolvedRunConfiguration Configuration, string Revision) : base(Configuration, Revision) { this.Room = Room; } } private sealed class OnlineRound : Round { internal Room Room { get; } internal int LocalActor { get; } internal string LocalReadyKey { get; } internal object[] ReadyValue { get; } internal RoomPropertyPublication ReadyPublication { get; } = new RoomPropertyPublication(); internal RoomPropertyPublication CommitPublication { get; } = new RoomPropertyPublication(); internal int MasterActor { get; private set; } internal string? MasterReadyKey { get; private set; } internal int[] Actors { get; private set; } internal string[] ActorReadyKeys { get; private set; } internal object[]? DesiredCommit { get; set; } internal OnlineRound(Room Room, ResolvedRunConfiguration Configuration, string Revision, int LocalActor, int MasterActor, int[] Actors) : base(Configuration, Revision) { this.Room = Room; this.LocalActor = LocalActor; LocalReadyKey = ReadyKey(LocalActor); this.Actors = Array.Empty<int>(); ActorReadyKeys = Array.Empty<string>(); ReplaceActors(Actors); ReplaceMaster(MasterActor); ReadyValue = CreateReadyValue(base.Epoch, Revision); } internal void ReplaceActors(int[] Actors) { this.Actors = Actors; ActorReadyKeys = new string[Actors.Length]; for (int i = 0; i < Actors.Length; i++) { ActorReadyKeys[i] = ((Actors[i] == LocalActor) ? LocalReadyKey : ReadyKey(Actors[i])); } RefreshMasterReadyKey(); ResetCommit(); } internal void ReplaceMaster(int Actor) { MasterActor = Actor; RefreshMasterReadyKey(); ResetCommit(); } internal void ResetCommit() { DesiredCommit = null; CommitPublication.Reset(); } private void RefreshMasterReadyKey() { MasterReadyKey = null; for (int i = 0; i < Actors.Length; i++) { if (Actors[i] == MasterActor) { MasterReadyKey = ActorReadyKeys[i]; break; } } } } private const string Protocol = "1"; private const string ReadyPrefix = "af.pll.library/state/final-ready/"; private const string CommitKey = "af.pll.library/state/final-commit"; private static Round? CurrentRound; internal static void Reset() { CurrentRound = null; } internal static void ConfirmOffline(Room? Room, ResolvedRunConfiguration Configuration, string Revision) { CurrentRound = new OfflineRound(Room, Configuration, Revision); } internal static IEnumerator Wait(ResolvedRunConfiguration Configuration) { Room currentRoom = PhotonNetwork.CurrentRoom; string manifestRevision = SegmentRegistry.ManifestRevision; if (currentRoom == null || currentRoom.IsOffline) { ConfirmOffline(currentRoom, Configuration, manifestRevision); yield break; } if (!currentRoom.BroadcastPropertiesChangeToAll) { throw PhotonRoomRejection.Disconnect("PLL Library cannot verify the final custom-content manifest without server property echoes."); } if ((int)PhotonNetwork.NetworkClientState != 9) { throw PhotonRoomRejection.Disconnect("PLL Library lost its Photon room before final custom-content validation."); } Player masterClient = PhotonNetwork.MasterClient; if (masterClient == null || masterClient.ActorNumber <= 0 || !currentRoom.Players.ContainsKey(masterClient.ActorNumber)) { throw PhotonRoomRejection.Disconnect("PLL Library could not identify the authoritative player for final custom-content validation."); } int actorNumber = PhotonNetwork.LocalPlayer.ActorNumber; if (actorNumber <= 0 || !currentRoom.Players.ContainsKey(actorNumber)) { throw PhotonRoomRejection.Disconnect("PLL Library could not identify the local player for final custom-content validation."); } if (!TryCurrentActors(currentRoom, out int[] Actors)) { throw PhotonRoomRejection.Disconnect("PLL Library found an invalid player set during final custom-content validation."); } OnlineRound Consensus = (OnlineRound)(CurrentRound = ((CurrentRound is OnlineRound onlineRound && onlineRound.Room == currentRoom && onlineRound.LocalActor == actorNumber && SameIdentity(onlineRound, Configuration, manifestRevision)) ? onlineRound : new OnlineRound(currentRoom, Configuration, manifestRevision, actorNumber, masterClient.ActorNumber, Actors))); while (true) { if (!IsCurrent(Consensus)) { if (CurrentRound == Consensus) { CurrentRound = null; } throw PhotonRoomRejection.Disconnect("PLL Library lost its Photon room before final custom-content validation."); } if (!Consensus.Room.BroadcastPropertiesChangeToAll) { throw PhotonRoomRejection.Disconnect("PLL Library cannot verify the final custom-content manifest without server property echoes."); } if (TryMaintainAndConfirm(Consensus)) { break; } yield return null; } } internal static void RequireConfirmed(ResolvedRunConfiguration Configuration) { Room currentRoom = PhotonNetwork.CurrentRoom; string manifestRevision = SegmentRegistry.ManifestRevision; if (currentRoom == null || currentRoom.IsOffline) { if (!(CurrentRound is OfflineRound offlineRound) || offlineRound.Room != currentRoom || !SameIdentity(offlineRound, Configuration, manifestRevision)) { CurrentRound = null; throw new InvalidOperationException("PLL Library's final custom-content manifest is not confirmed."); } return; } if (!(CurrentRound is OnlineRound onlineRound) || onlineRound.Room != currentRoom || !SameIdentity(onlineRound, Configuration, manifestRevision) || !IsCurrent(onlineRound)) { CurrentRound = null; throw PhotonRoomRejection.Disconnect("PLL Library's final custom-content manifest is not confirmed."); } if (!TryMaintainAndConfirm(onlineRound)) { throw PhotonRoomRejection.Disconnect("PLL Library's final custom-content manifest confirmation is no longer valid."); } } internal static FinalManifestConfirmation? CaptureConfirmation(ResolvedRunConfiguration Configuration) { RequireConfirmed(Configuration); if (!(CurrentRound is OnlineRound onlineRound)) { return null; } return new FinalManifestConfirmation(onlineRound.Room, Configuration, onlineRound.Revision, onlineRound.LocalActor, onlineRound.Actors); } internal static bool Owns(ResolvedRunConfiguration Configuration, Room Room) { if (CurrentRound is OnlineRound onlineRound && onlineRound.Room == Room && SameIdentity(onlineRound, Configuration, SegmentRegistry.ManifestRevision)) { return IsCurrent(onlineRound); } return false; } internal static bool TryMaintainConfirmation(ResolvedRunConfiguration Configuration) { if (!(CurrentRound is OnlineRound onlineRound) || onlineRound.Configuration != Configuration || !IsCurrent(onlineRound)) { throw PhotonRoomRejection.Disconnect("PLL Library's final custom-content manifest is not confirmed for initial generation."); } return TryMaintainAndConfirm(onlineRound); } internal static void ConnectionStateChanged(ClientState Current) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0003: Unknown result type (might be due to invalid IL or missing references) //IL_0005: Invalid comparison between Unknown and I4 //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 if (Current - 13 <= 1) { CurrentRound = null; } else if ((int)Current == 9) { MaintainCurrent(); } } private static string ReadyKey(int Actor) { return "af.pll.library/state/final-ready/" + Actor.ToString(CultureInfo.InvariantCulture); } private static object[] CreateReadyValue(string Epoch, string Revision) { return new object[3] { "1", Epoch, Revision }; } private static object[] CreateCommitValue(string Epoch, string Revision, int MasterActor, int[] Actors) { return new object[5] { "1", Epoch, Revision, MasterActor, Actors }; } private static bool SameIdentity(Round Consensus, ResolvedRunConfiguration Configuration, string Revision) { if (Consensus.Configuration == Configuration) { return string.Equals(Consensus.Revision, Revision, StringComparison.Ordinal); } return false; } private static bool IsCurrent(OnlineRound Consensus) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Invalid comparison between Unknown and I4 if (CurrentRound == Consensus && PhotonNetwork.CurrentRoom == Consensus.Room && (int)PhotonNetwork.NetworkClientState == 9) { return string.Equals(Consensus.Revision, SegmentRegistry.ManifestRevision, StringComparison.Ordinal); } return false; } private static bool TryCurrentActors(Room Room, out int[] Actors) { Actors = new int[Room.Players.Count]; int num = 0; foreach (int key in Room.Players.Keys) { if (key <= 0 || num == Actors.Length) { return false; } Actors[num++] = key; } if (num != Actors.Length || num == 0) { return false; } Array.Sort(Actors); return true; } private static bool CurrentActorsMatch(OnlineRound Consensus) { if (Consensus.Actors.Length != Consensus.Room.Players.Count) { return false; } int[] actors = Consensus.Actors; foreach (int key in actors) { if (!Consensus.Room.Players.ContainsKey(key)) { return false; } } return true; } private static bool RefreshTopology(OnlineRound Consensus) { if (!CurrentActorsMatch(Consensus)) { if (!TryCurrentActors(Consensus.Room, out int[] Actors)) { return false; } Consensus.ReplaceActors(Actors); } Player masterClient = PhotonNetwork.MasterClient; int num = ((masterClient != null) ? masterClient.ActorNumber : 0); if (num <= 0 || !Consensus.Room.Players.ContainsKey(num)) { if (Consensus.MasterActor != 0) { Consensus.ReplaceMaster(0); } return false; } if (Consensus.MasterActor != num) { Consensus.ReplaceMaster(num); } return Consensus.Room.Players.ContainsKey(Consensus.LocalActor); } private static bool TryMaintainAndConfirm(OnlineRound Consensus) { bool LocalReady; bool flag = MaintainLocal(Consensus, out LocalReady); string text = PublicationFailure(Consensus); if (text != null) { throw PhotonRoomRejection.Disconnect(text); } if (!flag || !LocalReady) { return false; } if (Consensus.MasterActor == Consensus.LocalActor) { bool CommitEchoed; bool flag2 = MaintainMaster(Consensus, ValidateIdentity: true, out CommitEchoed); string text2 = PublicationFailure(Consensus); if (text2 != null) { throw PhotonRoomRejection.Disconnect(text2); } if (flag2) { if (!CommitEchoed) { return CommitMatches(((RoomInfo)Consensus.Room).CustomProperties[(object)"af.pll.library/state/final-commit"], Consensus); } return true; } return false; } if (Consensus.MasterReadyKey == null || !ReadyMatches(((RoomInfo)Consensus.Room).CustomProperties[(object)Consensus.MasterReadyKey], Consensus, Consensus.MasterActor, ValidateIdentity: true)) { return false; } if (!CommitMatches(((RoomInfo)Consensus.Room).CustomProperties[(object)"af.pll.library/state/final-commit"], Consensus)) { return false; } return CurrentReadySetMatches(Consensus, ValidateIdentity: true, Consensus.MasterActor); } private static bool MaintainLocal(OnlineRound Consensus, out bool LocalReady) { LocalReady = false; if (PublicationFailure(Consensus) != null || !IsCurrent(Consensus) || !RefreshTopology(Consensus)) { return false; } LocalReady = Consensus.ReadyPublication.AwaitEcho(Consensus.Room, Consensus.LocalReadyKey, Consensus.ReadyValue, "PLL Library could not publish its final custom-content manifest."); return true; } private static bool MaintainMaster(OnlineRound Consensus, bool ValidateIdentity, out bool CommitEchoed) { bool flag = CurrentReadySetMatches(Consensus, ValidateIdentity, 0); CommitEchoed = MaintainCommit(Consensus, flag); return flag; } private static bool MaintainCommit(OnlineRound Consensus, bool AllReady) { if (!AllReady) { Consensus.ResetCommit(); return false; } if (Consensus.DesiredCommit == null) { object[] array = (Consensus.DesiredCommit = CreateCommitValue(Consensus.Epoch, Consensus.Revision, Consensus.MasterActor, Consensus.Actors)); } return Consensus.CommitPublication.AwaitEcho(Consensus.Room, "af.pll.library/state/final-commit", Consensus.DesiredCommit, "PLL Library could not publish the final custom-content manifest release."); } private static string? PublicationFailure(OnlineRound Consensus) { return Consensus.ReadyPublication.Failure ?? Consensus.CommitPublication.Failure; } internal static void MaintainCurrent() { Room currentRoom = PhotonNetwork.CurrentRoom; if (CurrentRound is OfflineRound offlineRound) { if (offlineRound.Room != currentRoom || (currentRoom != null && !currentRoom.IsOffline) || !string.Equals(offlineRound.Revision, SegmentRegistry.ManifestRevision, StringComparison.Ordinal)) { CurrentRound = null; } } else { if (!(CurrentRound is OnlineRound onlineRound)) { return; } bool LocalReady; if (!IsCurrent(onlineRound)) { if (CurrentRound == onlineRound) { CurrentRound = null; } } else if (MaintainLocal(onlineRound, out LocalReady) && LocalReady && PublicationFailure(onlineRound) == null && onlineRound.MasterActor == onlineRound.LocalActor) { MaintainMaster(onlineRound, ValidateIdentity: false, out var _); } } } private static bool CurrentReadySetMatches(OnlineRound Consensus, bool ValidateIdentity, int ExcludedActor) { for (int i = 0; i < Consensus.Actors.Length; i++) { int num = Consensus.Actors[i]; if (num != Consensus.LocalActor && num != ExcludedActor && !ReadyMatches(((RoomInfo)Consensus.Room).CustomProperties[(object)Consensus.ActorReadyKeys[i]], Consensus, num, ValidateIdentity)) { return false; } } return true; } private static bool ReadyMatches(object? Value, OnlineRound Consensus, int Actor, bool ValidateIdentity) { if (!(Value is object[] array) || array.Length < 2 || !(array[1] is string a) || !string.Equals(a, Consensus.Epoch, StringComparison.Ordinal)) { return false; } if (array.Length != 3 || !(array[0] is string a2) || !(array[2] is string a3)) { if (ValidateIdentity) { throw PhotonRoomRejection.Disconnect($"Player {Actor} published a malformed final custom-content manifest."); } return false; } if (!string.Equals(a2, "1", StringComparison.Ordinal)) { if (ValidateIdentity) { throw PhotonRoomRejection.Disconnect($"Player {Actor} uses an incompatible final custom-content protocol."); } return false; } if (string.Equals(a3, Consensus.Revision, StringComparison.Ordinal)) { return true; } if (ValidateIdentity) { throw PhotonRoomRejection.Disconnect($"Player {Actor} has different surviving PLL content."); } return false; } private static bool CommitMatches(object? Value, OnlineRound Consensus) { if (!(Value is object[] array) || array.Length < 2 || !(array[1] is string a) || !string.Equals(a, Consensus.Epoch, StringComparison.Ordinal)) { return false; } if (array.Length != 5 || !(array[0] is string a2) || !(array[2] is string a3) || !(array[3] is int num) || !(array[4] is int[] array2)) { throw PhotonRoomRejection.Disconnect("The host published a malformed final custom-content manifest release."); } if (num != Consensus.MasterActor) { return false; } if (!string.Equals(a2, "1", StringComparison.Ordinal)) { throw PhotonRoomRejection.Disconnect("The host uses an incompatible final custom-content protocol."); } if (!string.Equals(a3, Consensus.Revision, StringComparison.Ordinal)) { throw PhotonRoomRejection.Disconnect("The host has different surviving PLL content."); } if (array2.Length != Consensus.Actors.Length) { return false; } for (int i = 0; i < array2.Length; i++) { if (array2[i] != Consensus.Actors[i]) { return false; } } return true; } } internal static class InitialGenerationCohortBarrier { private enum PropertyState { Absent, Valid, Invalid } private const string Protocol = "1"; private const string CohortKey = "af.pll.library/state/generation/cohort"; private const string ReadyPrefix = "af.pll.library/state/generation/cohort-ready/"; internal static IEnumerator Wait(ResolvedRunConfiguration Configuration, Action<MapRun.GenerationCohort?> Capture) { if (Configuration == null) { throw new ArgumentNullException("Configuration"); } if (Capture == null) { throw new ArgumentNullException("Capture"); } FinalManifestConfirmation Confirmation = FinalManifestConsensus.CaptureConfirmation(Configuration); if (Confirmation == null) { Capture(null); yield break; } Room Room = Confirmation.Room; MapRun.GenerationCohort ProposedCohort = new MapRun.GenerationCohort(Configuration.Protocol.GenerationEpoch, Confirmation.Revision, Confirmation.SnapshotActors()); object[] ProposedValue = Encode(ProposedCohort); RoomPropertyPublication CohortPublication = new RoomPropertyPublication(); RoomPropertyPublication ReadyPublication = new RoomPropertyPublication(); MapRun.GenerationCohort Cohort = null; object[] CohortValue = null; string[] ReadyKeys = null; string LocalReadyKey = null; object CachedProperty = null; MapRun.GenerationCohort CachedCohort = null; string CachedFailure = null; PropertyState CachedState = PropertyState.Absent; bool PropertyCached = false; while (true) { if (PhotonNetwork.CurrentRoom != Room || (int)PhotonNetwork.NetworkClientState != 9 || !FinalManifestConsensus.Owns(Configuration, Room)) { throw PhotonRoomRejection.Disconnect("PLL Library lost its Photon room before initial generation was sealed."); } if (!Room.BroadcastPropertiesChangeToAll) { break; } bool flag = FinalManifestConsensus.TryMaintainConfirmation(Configuration); object obj = ((RoomInfo)Room).CustomProperties[(object)"af.pll.library/state/generation/cohort"]; PropertyState propertyState; MapRun.GenerationCohort Cohort2; string Failure; if (PropertyCached && obj == CachedProperty) { propertyState = CachedState; Cohort2 = CachedCohort; Failure = CachedFailure; } else { if (CohortValue != null && PhotonPropertyProtocol.SameValue(obj, CohortValue)) { propertyState = PropertyState.Valid; Cohort2 = Cohort; Failure = null; } else { propertyState = Read(obj, Configuration.Protocol.GenerationEpoch, Confirmation.Revision, out Cohort2, out Failure); } CachedProperty = obj; CachedState = propertyState; CachedCohort = Cohort2; CachedFailure = Failure; PropertyCached = true; } switch (propertyState) { case PropertyState.Invalid: throw PhotonRoomRejection.Disconnect(Failure); case PropertyState.Absent: if (Cohort != null) { throw PhotonRoomRejection.Disconnect("The initial generation cohort disappeared before release."); } RequireActorsPresent(Room, ProposedCohort); if (PhotonNetwork.IsMasterClient) { CohortPublication.AwaitEcho(Room, "af.pll.library/state/generation/cohort", ProposedValue, "PLL Library could not publish the initial generation cohort."); string failure = CohortPublication.Failure; if (failure != null) { throw PhotonRoomRejection.Disconnect(failure); } } yield return null; continue; } if (Cohort == null) { Cohort = Cohort2; CohortValue = Encode(Cohort); ReadyKeys = new string[Cohort.Count]; for (int i = 0; i < ReadyKeys.Length; i++) { int num = Cohort.ActorAt(i); string text = (ReadyKeys[i] = ReadyKey(num)); if (num == Confirmation.LocalActor) { LocalReadyKey = text; } } } else if (!Cohort.Matches(Cohort2)) { throw PhotonRoomRejection.Disconnect("The initial generation cohort changed before release."); } if (LocalReadyKey == null) { if (!flag) { yield return null; continue; } Capture(Cohort); yield break; } RequireActorsPresent(Room, Cohort); bool flag2 = ReadyPublication.AwaitEcho(Room, LocalReadyKey, CohortValue, "PLL Library could not confirm its initial generation cohort."); string failure2 = ReadyPublication.Failure; if (failure2 != null) { throw PhotonRoomRejection.Disconnect(failure2); } if (flag2 && flag && AllActorsReady(Room, Cohort, CohortValue, ReadyKeys)) { Capture(Cohort); yield break; } yield return null; } throw PhotonRoomRejection.Disconnect("PLL Library cannot seal initial generation without server property echoes."); } internal static void RequireCurrent(MapRun.GenerationCohort Cohort, object[] Expected, Room Room) { if (PhotonNetwork.CurrentRoom != Room) { throw new InvalidOperationException("Initial generation synchronization lost its Photon room."); } object obj = ((RoomInfo)Room).CustomProperties[(object)"af.pll.library/state/generation/cohort"]; if (!PhotonPropertyProtocol.SameValue(obj, Expected)) { MapRun.GenerationCohort Cohort2; string Failure; PropertyState propertyState = Read(obj, Cohort.GenerationEpoch, Cohort.FinalManifestRevision, out Cohort2, out Failure); if (propertyState != PropertyState.Valid) { throw new InvalidOperationException(Failure ?? "The initial generation cohort is unavailable."); } if (!Cohort.Matches(Cohort2)) { throw new InvalidOperationException("The initial generation cohort changed after map preparation."); } } } internal static bool TryFindMissingActor(MapRun.GenerationCohort Cohort, Room Room, out int Actor) { for (int i = 0; i < Cohort.Count; i++) { Actor = Cohort.ActorAt(i); if (!Room.Players.ContainsKey(Actor)) { return true; } } Actor = 0; return false; } private static string ReadyKey(int Actor) { return "af.pll.library/state/generation/cohort-ready/" + Actor.ToString(CultureInfo.InvariantCulture); } internal static object[] Encode(MapRun.GenerationCohort Cohort) { return new object[4] { "1", Cohort.GenerationEpoch, Cohort.FinalManifestRevision, Cohort.SnapshotActors() }; } private static PropertyState Read(object? Value, string Epoch, string Revision, out MapRun.GenerationCohort Cohort, out string? Failure) { Cohort = null; Failure = null; if (Value == null || (Value is object[] array && array.Length == 0)) { return PropertyState.Absent; } if (Value is object[] array2 && array2.Length > 1 && array2[1] is string a && !string.Equals(a, Epoch, StringComparison.Ordinal)) { return PropertyState.Absent; } if (!(Value is object[] array3) || array3.Length != 4 || !(array3[0] is string a2) || !(array3[1] is string text) || !(array3[2] is string text2) || !(array3[3] is int[] array4)) { Failure = "The initial generation cohort property is malformed."; return PropertyState.Invalid; } if (!string.Equals(a2, "1", StringComparison.Ordinal)) { Failure = "The initial generation cohort protocol is incompatible."; return PropertyState.Invalid; } if (!string.Equals(text, Epoch, StringComparison.Ordinal)) { return PropertyState.Absent; } if (!string.Equals(text2, Revision, StringComparison.Ordinal)) { Failure = "The initial generation cohort has a different final manifest revision."; return PropertyState.Invalid; } try { Cohort = new MapRun.GenerationCohort(text, text2, array4); return PropertyState.Valid; } catch (ArgumentException) { Failure = ((array4.Length == 0) ? "The initial generation cohort has no actors." : "The initial generation cohort actor list is invalid."); return PropertyState.Invalid; } } private static void RequireActorsPresent(Room Room, MapRun.GenerationCohort Cohort) { if (TryFindMissingActor(Cohort, Room, out var Actor)) { throw PhotonRoomRejection.Disconnect($"Player {Actor} left before the initial generation cohort was released."); } } private static bool AllActorsReady(Room Room, MapRun.GenerationCohort Cohort, object[] Expected, string[] ReadyKeys) { for (int i = 0; i < Cohort.Count; i++) { int num = Cohort.ActorAt(i); object obj = ((RoomInfo)Room).CustomProperties[(object)ReadyKeys[i]]; if (PhotonPropertyProtocol.SameValue(obj, Expected)) { continue; } MapRun.GenerationCohort Cohort2; string Failure; switch (Read(obj, Cohort.GenerationEpoch, Cohort.FinalManifestRevision, out Cohort2, out Failure)) { case PropertyState.Absent: return false; case PropertyState.Invalid: throw PhotonRoomRejection.Disconnect($"Player {num} published an invalid initial generation cohort echo."); } if (!Cohort.Matches(Cohort2)) { throw PhotonRoomRejection.Disconnect($"Player {num} published a different initial generation cohort echo."); } } return true; } } internal static class IslandLoadPublication { internal sealed class Publication { internal Room Room { get; } internal AirportCheckInKiosk? Kiosk { get; } internal int Ascent { get; } internal byte[]? SerializedRunSettings { get; } internal string Payload { get; } internal Hashtable Properties { get; } internal bool Confirmed { get; set; } internal string? Failure { get; set; } internal Publication(Room Room, AirportCheckInKiosk? Kiosk, int Ascent, byte[]? SerializedRunSettings, string Payload, Hashtable Properties) { this.Room = Room; this.Kiosk = Kiosk; this.Ascent = Ascent; this.SerializedRunSettings = SerializedRunSettings; this.Payload = Payload; this.Properties = Properties; } } [HarmonyPatch(typeof(LoadingScreenHandler), "LoadSceneProcess", new Type[] { typeof(string), typeof(bool), typeof(bool), typeof(float) })] private static class ProcessingSceneLoadPatch { [HarmonyPostfix] private static void Postfix(string sceneName, bool networked, ref IEnumerator __result) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Invalid comparison between Unknown and I4 Room currentRoom = PhotonNetwork.CurrentRoom; if (networked && !PhotonNetwork.OfflineMode && currentRoom != null && (int)PhotonNetwork.NetworkClientState == 9 && string.Equals(sceneName, "WilIsland", StringComparison.Ordinal)) { __result = HoldProcessingSceneLoad(__result, currentRoom); } } } [HarmonyPatch(typeof(AirportCheckInKiosk), "LoadIslandMaster", new Type[] { typeof(int), typeof(byte[]) })] private static class AirportLoadPatch { [HarmonyTranspiler] private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> Instructions) { MethodInfo GetLevel = AccessTools.DeclaredMethod(typeof(MapBaker), "GetLevel", new Type[1] { typeof(int) }, (Type[])null) ?? throw new MissingMethodException("MapBaker.GetLevel(int) was not found."); MethodInfo Selector = new Func<string, string>(SelectMapBakerScene).Method; int Replaced = 0; foreach (CodeInstruction Instruction in Instructions) { yield return Instruction; if (CodeInstructionExtensions.Calls(Instruction, GetLevel)) { yield return new CodeInstruction(OpCodes.Call, (object)Selector); Replaced++; } } if (Replaced != 1) { throw new MissingMethodException($"AirportCheckInKiosk.LoadIslandMaster's MapBaker.GetLevel call was found {Replaced} times."); } } [HarmonyPrefix] private static bool Prefix(AirportCheckInKiosk __instance, int ascent, byte[] serializedRunSettings) { Publication releasing = Releasing; if (releasing != null) { if (releasing.Kiosk == __instance && releasing.Ascent == ascent) { return releasing.SerializedRunSettings == serializedRunSettings; } return false; } if (LoadingScreenHandler.loading || serializedRunSettings == null) { return false; } Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom == null) { return false; } Publication pending = Pending; if (pending != null) { Cancel(pending); } if (!currentRoom.IsOffline && !currentRoom.BroadcastPropertiesChangeToAll) { Plugin.PLLLog.LogError((object)"PLL Library cannot verify room configuration publication without server property echoes."); return false; } return Publish(__instance, ascent, serializedRunSettings) == null; } } private static Publication? Pending; private static Publication? Releasing; internal static Publication PublishQuicksave(CapturedLayoutSelection Selection) { return Publish(null, 0, null, Selection) ?? throw new InvalidOperationException("PLL Library cannot publish a room configuration without host authority."); } internal static bool Owns(Publication Request) { return Pending == Request; } internal static bool Consume(Publication Request) { if (Pending != Request) { return false; } Pending = null; return true; } internal static void Cancel(Publication Request) { if (Pending == Request) { Pending = null; } } internal static void Fail(Publication Request, string Message) { if (Pending == Request) { Request.Failure = Message; Pending = null; Plugin.PLLLog.LogError((object)Message); } } internal static bool HasServerEcho(Publication Request) { foreach (object key in ((Dictionary<object, object>)(object)Request.Properties).Keys) { if (!((Dictionary<object, object>)(object)((RoomInfo)Request.Room).CustomProperties).TryGetValue(key, out object value) || !PhotonPropertyProtocol.SameValue(value, Request.Properties[key])) { return false; } } foreach (object key2 in ((Dictionary<object, object>)(object)((RoomInfo)Request.Room).CustomProperties).Keys) { if (key2 is string text && text.StartsWith("af.pll.library/state/", StringComparison.Ordinal) && (!(((RoomInfo)Request.Room).CustomProperties[key2] is object[] array) || array.Length != 0)) { return false; } } return true; } internal static void Tick() { Publication pending = Pending; if (pending != null) { if (PhotonNetwork.CurrentRoom != pending.Room || !PhotonNetwork.IsMasterClient) { Fail(pending, "PLL Library cancelled the pending room configuration because room authority changed."); } else { TryConfirm(pending); } } } internal static void RoomPropertiesChanged(Hashtable PropertiesThatChanged) { Publication pending = Pending; if (pending != null) { object value; if (PhotonNetwork.CurrentRoom != pending.Room || !PhotonNetwork.IsMasterClient) { Fail(pending, "PLL Library cancelled the pending room configuration because room authority changed."); } else if (((Dictionary<object, object>)(object)PropertiesThatChanged).TryGetValue((object)"af.pll.library/c", out value) && value is string a && string.Equals(a, pending.Payload, StringComparison.Ordinal)) { TryConfirm(pending); } } } internal static void ConnectionStateChanged(ClientState Current) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Invalid comparison between Unknown and I4 Publication pending = Pending; if (pending != null && !pending.Room.IsOffline && (int)Current != 9) { Fail(pending, "PLL Library cancelled the pending room configuration because the room connection changed."); } } internal static void MasterClientChanged() { Publication pending = Pending; if (pending != null) { Fail(pending, "PLL Library cancelled the pending room configuration because the master client changed."); } } private static Publication? Publish(AirportCheckInKiosk? Kiosk, int Ascent, byte[]? SerializedRunSettings, CapturedLayoutSelection? RestoredSelection = null) { //IL_0063: Unknown result type (might be due to invalid IL or missing references) //IL_006f: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom == null || !PhotonNetwork.IsMasterClient) { return null; } if (Pending != null) { throw new InvalidOperationException("PLL Library already has a pending room configuration."); } SegmentRegistry.SealStartup(); ResolvedRunConfiguration configuration = RunConfigurationCodec.CaptureLocal(RestoredSelection); if ((Object)(object)Kiosk != (Object)null && SerializedRunSettings != null && MapQuicksave.RuntimeLayoutPublicationRequired(configuration) && !MapQuicksave.ClearBufferedCampfireRpcs()) { throw new InvalidOperationException("PLL Library could not clear stale PEAK campfire state before loading the island."); } RunConfigurationResolution.AdoptForPublication(configuration); string text = RunConfigurationCodec.Serialize(configuration); Hashtable val = new Hashtable { [(object)"af.pll.library/c"] = text }; foreach (object key in ((Dictionary<object, object>)(object)((RoomInfo)currentRoom).CustomProperties).Keys) { if (key is string text2 && text2.StartsWith("af.pll.library/state/", StringComparison.Ordinal)) { val[key] = Array.Empty<object>(); } } Publication publication = (Pending = new Publication(currentRoom, Kiosk, Ascent, SerializedRunSettings, text, val));