Please disclose if any significant portion of your mod was created using AI tools by adding the 'AI Generated' category. Failing to do so may result in the mod being removed from Thunderstore.
Decompiled source of EliteBuildingPieces v0.1.0
plugins\EliteBuildingPieces.dll
Decompiled a day 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.Concurrent; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using BundlePrefabs; using Charter; using ConfigReload; using EliteBuildingPieces.Core; using EliteBuildingPieces.CoreWood; using HarmonyLib; using Microsoft.CodeAnalysis; using PatchGuard; using SyncedConfig; using UnityEngine; using YamlConfig; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.Serialization; using YamlDotNet.Serialization.BufferedDeserialization; using YamlDotNet.Serialization.BufferedDeserialization.TypeDiscriminators; using YamlDotNet.Serialization.Callbacks; using YamlDotNet.Serialization.Converters; using YamlDotNet.Serialization.EventEmitters; using YamlDotNet.Serialization.NamingConventions; using YamlDotNet.Serialization.NodeDeserializers; using YamlDotNet.Serialization.NodeTypeResolvers; using YamlDotNet.Serialization.ObjectFactories; using YamlDotNet.Serialization.ObjectGraphTraversalStrategies; using YamlDotNet.Serialization.ObjectGraphVisitors; using YamlDotNet.Serialization.Schemas; using YamlDotNet.Serialization.TypeInspectors; using YamlDotNet.Serialization.TypeResolvers; using YamlDotNet.Serialization.Utilities; using YamlDotNet.Serialization.ValueDeserializers; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: IgnoresAccessChecksTo("assembly_guiutils")] [assembly: IgnoresAccessChecksTo("assembly_utils")] [assembly: IgnoresAccessChecksTo("assembly_valheim")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyCompany("EliteBuildingPieces")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("EliteBuildingPieces: new building pieces, starting with core wood walls and doors")] [assembly: AssemblyFileVersion("0.1.0.0")] [assembly: AssemblyInformationalVersion("0.1.0+0cf3be3418919774cf3d3c90f9043bcbb4d8d38a")] [assembly: AssemblyProduct("EliteBuildingPieces")] [assembly: AssemblyTitle("EliteBuildingPieces")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("0.1.0.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace EliteBuildingPieces { [BepInPlugin("milkyteam.elitebuildingpieces", "EliteBuildingPieces", "0.1.0")] public class Plugin : BaseUnityPlugin { public const string PluginGuid = "milkyteam.elitebuildingpieces"; public const string PluginName = "EliteBuildingPieces"; public const string PluginVersion = "0.1.0"; public static ManualLogSource Log { get; private set; } public static SyncedConfiguration Synced { get; private set; } public static ConfigEntry<bool> LockConfiguration { get; private set; } private void Awake() { //IL_0056: Unknown result type (might be due to invalid IL or missing references) //IL_005c: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; Synced = new SyncedConfiguration((BaseUnityPlugin)(object)this, ((BaseUnityPlugin)this).Logger, "EliteBuildingPieces", "0.1.0"); LockConfiguration = Synced.BindLocking("General", "Lock Configuration", defaultValue: true, "[Server Only] Clients cannot change synced settings while connected to a server that has this on."); CoreWoodModule.Initialize(Synced); Harmony harmony = new Harmony("milkyteam.elitebuildingpieces"); int num = PatchEverything(harmony); Synced.Finish(harmony); Log.LogInfo((object)("Loading [EliteBuildingPieces 0.1.0]" + ((num > 0) ? $" with {num} failed patches, see above" : ""))); Guard.Install(harmony, ((BaseUnityPlugin)this).Logger, Assembly.GetExecutingAssembly()); } private static int PatchEverything(Harmony harmony) { int num = 0; Type[] typesFromAssembly = AccessTools.GetTypesFromAssembly(Assembly.GetExecutingAssembly()); foreach (Type type in typesFromAssembly) { if (type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true).Length != 0) { try { harmony.CreateClassProcessor(type).Patch(); } catch (Exception ex) { num++; Log.LogError((object)("Patch " + type.FullName + " failed to apply: " + ex.Message)); } } } return num; } } } namespace EliteBuildingPieces.Core { public static class Language { private static readonly Dictionary<string, string> words = new Dictionary<string, string>(); public static string Add(string key, string english) { words[key] = english; if (Localization.instance != null) { Localization.instance.AddWord(key, english); } return "$" + key; } internal static void Install(Localization localization) { foreach (KeyValuePair<string, string> word in words) { localization.AddWord(word.Key, word.Value); } } } [HarmonyPatch(typeof(Localization), "SetupLanguage")] public static class LanguageSetupPatch { [HarmonyPostfix] public static void Postfix(Localization __instance) { Language.Install(__instance); } } } namespace EliteBuildingPieces.CoreWood { [HarmonyPatch] public static class CoreWoodHammer { private const string Hammer = "Hammer"; private const string CoreWood = "RoundLog"; [HarmonyPatch(typeof(ObjectDB), "Awake")] [HarmonyPostfix] [HarmonyPriority(0)] private static void DatabaseAwake() { Refresh(); } [HarmonyPatch(typeof(ZNetScene), "Awake")] [HarmonyPostfix] private static void SceneAwake() { Refresh(); } public static void Refresh() { try { Apply(ObjectDB.instance); } catch (Exception ex) { Plugin.Log.LogError((object)("EliteBuildingPieces: the core wood pieces could not be put in the hammer: " + ex.Message)); } } private static void Apply(ObjectDB database) { PieceTable val = HammerTable(database); if ((Object)(object)val == (Object)null) { return; } GameObject itemPrefab = database.GetItemPrefab("RoundLog"); CoreWoodPiece[] all = CoreWoodPieces.All; foreach (CoreWoodPiece coreWoodPiece in all) { if (!((Object)(object)coreWoodPiece.Built == (Object)null)) { val.m_pieces.Remove(coreWoodPiece.Built); Configure(coreWoodPiece.Built.GetComponent<Piece>(), coreWoodPiece.Cost, ((Object)(object)itemPrefab != (Object)null) ? itemPrefab.GetComponent<ItemDrop>() : null); } } if (CoreWoodSettings.On) { Insert(val.m_pieces); } } private static void Configure(Piece piece, int cost, ItemDrop wood) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002c: Unknown result type (might be due to invalid IL or missing references) //IL_0034: Expected O, but got Unknown piece.m_category = (PieceCategory)2; if ((Object)(object)wood != (Object)null) { piece.m_resources = (Requirement[])(object)new Requirement[1] { new Requirement { m_resItem = wood, m_amount = cost, m_recover = true } }; } } private static void Insert(List<GameObject> pieces) { int num = pieces.FindIndex((GameObject p) => (Object)(object)p != (Object)null && ((Object)p).name == "wood_gate"); num = ((num < 0) ? pieces.Count : (num + 1)); CoreWoodPiece[] all = CoreWoodPieces.All; foreach (CoreWoodPiece coreWoodPiece in all) { if ((Object)(object)coreWoodPiece.Built != (Object)null) { pieces.Insert(num++, coreWoodPiece.Built); } } } private static PieceTable HammerTable(ObjectDB database) { GameObject val = (((Object)(object)database != (Object)null) ? database.GetItemPrefab("Hammer") : null); ItemDrop val2 = (((Object)(object)val != (Object)null) ? val.GetComponent<ItemDrop>() : null); if (!((Object)(object)val2 != (Object)null)) { return null; } return val2.m_itemData.m_shared.m_buildPieces; } } public static class CoreWoodModel { private const string MaterialSource = "wood_wall_log"; private const string MaterialName = "logwall"; private const string Shader = "Custom/Piece"; private static readonly Dictionary<string, AssetBundle> bundles = new Dictionary<string, AssetBundle>(); public static bool Wear(GameObject piece, CoreWoodPiece def, Material wood) { AssetBundle val = Load(def.Bundle); GameObject val2 = (((Object)(object)val != (Object)null) ? Model(val, def.Bundle) : null); if ((Object)(object)val2 == (Object)null) { return false; } Strip(piece); GameObject val3 = Object.Instantiate<GameObject>(val2, piece.transform, false); ((Object)val3).name = "corewood"; Animator[] componentsInChildren = val3.GetComponentsInChildren<Animator>(true); for (int i = 0; i < componentsInChildren.Length; i++) { Object.DestroyImmediate((Object)(object)componentsInChildren[i]); } Dress(val3, wood); Unmirror(val3); SetLayer(val3.transform, piece.layer); CoreWoodSnaps.Lift(piece.transform, val3.transform); Point(piece.GetComponent<WearNTear>(), val3); if (def.IsDoor) { piece.AddComponent<GateSwing>(); } SetIcon(piece.GetComponent<Piece>(), val); return true; } public static Material LogWall(List<GameObject> prefabs) { GameObject val = prefabs.Find((GameObject p) => (Object)(object)p != (Object)null && ((Object)p).name == "wood_wall_log"); if ((Object)(object)val == (Object)null) { return null; } MeshRenderer[] componentsInChildren = val.GetComponentsInChildren<MeshRenderer>(true); for (int num = 0; num < componentsInChildren.Length; num++) { Material[] sharedMaterials = ((Renderer)componentsInChildren[num]).sharedMaterials; foreach (Material val2 in sharedMaterials) { if ((Object)(object)val2 != (Object)null && ((Object)val2).name == "logwall" && ((Object)val2.shader).name == "Custom/Piece") { return val2; } } } Plugin.Log.LogWarning((object)"EliteBuildingPieces: wood_wall_log has no logwall: the core wood pieces keep the bundle's materials"); return null; } private static AssetBundle Load(string name) { if (bundles.TryGetValue(name, out var value)) { return value; } try { value = EmbeddedBundle.Load(typeof(CoreWoodModel).Assembly, name); } catch (Exception ex) { Plugin.Log.LogWarning((object)("EliteBuildingPieces: the model " + name + " did not load: " + ex.Message)); } bundles[name] = value; return value; } private static GameObject Model(AssetBundle bundle, string name) { GameObject val = bundle.LoadAsset<GameObject>(name); if ((Object)(object)val != (Object)null) { return val; } GameObject[] array = bundle.LoadAllAssets<GameObject>(); if (array.Length == 0) { return null; } return array[0]; } private static void Strip(GameObject piece) { for (int num = piece.transform.childCount - 1; num >= 0; num--) { Object.DestroyImmediate((Object)(object)((Component)piece.transform.GetChild(num)).gameObject); } Type[] array = new Type[4] { typeof(LODGroup), typeof(Renderer), typeof(MeshFilter), typeof(Collider) }; foreach (Type type in array) { Component[] components = piece.GetComponents(type); for (int j = 0; j < components.Length; j++) { Object.DestroyImmediate((Object)(object)components[j]); } } } private static void Dress(GameObject placed, Material wood) { if ((Object)(object)wood == (Object)null) { return; } Renderer[] componentsInChildren = placed.GetComponentsInChildren<Renderer>(true); foreach (Renderer val in componentsInChildren) { Material[] sharedMaterials = val.sharedMaterials; for (int j = 0; j < sharedMaterials.Length; j++) { sharedMaterials[j] = wood; } val.sharedMaterials = sharedMaterials; } } private static void Unmirror(GameObject placed) { //IL_0016: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_001e: 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_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0044: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) //IL_006c: Unknown result type (might be due to invalid IL or missing references) //IL_006e: Unknown result type (might be due to invalid IL or missing references) BoxCollider[] componentsInChildren = placed.GetComponentsInChildren<BoxCollider>(true); Vector3 val2 = default(Vector3); foreach (BoxCollider val in componentsInChildren) { Vector3 localScale = ((Component)val).transform.localScale; ((Vector3)(ref val2))..ctor(Mathf.Sign(localScale.x), Mathf.Sign(localScale.y), Mathf.Sign(localScale.z)); if (!(val2 == Vector3.one)) { val.center = Vector3.Scale(val.center, val2); ((Component)val).transform.localScale = Vector3.Scale(localScale, val2); } } } private static void SetLayer(Transform part, int layer) { //IL_001b: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown ((Component)part).gameObject.layer = layer; foreach (Transform item in part) { SetLayer(item, layer); } } private static void Point(WearNTear wear, GameObject placed) { if (!((Object)(object)wear == (Object)null)) { wear.m_new = placed; wear.m_worn = placed; wear.m_broken = placed; wear.m_wet = null; wear.m_fragmentRoots = null; } } private static void SetIcon(Piece piece, AssetBundle bundle) { Sprite[] array = bundle.LoadAllAssets<Sprite>(); if ((Object)(object)piece != (Object)null && array.Length != 0) { piece.m_icon = array[0]; } } } public static class CoreWoodSettings { public static ConfigEntry<bool> Enabled { get; private set; } public static bool On { get { if (Enabled != null) { return Enabled.Value; } return false; } } public static void Initialize(SyncedConfiguration synced) { Enabled = synced.Bind("1. Core Wood", "Core Wood Pieces", true, "Adds four core wood pieces to the hammer's Building tab, after the wood gate: a wall (4 core wood), a large wall twice as wide (8), a door (6) and a double door (12). The walls snap and hold like the stakewall; the doors open away from you like the wood gate. Off: they leave the hammer, pieces already built stay.", true, null); } } public static class CoreWoodModule { public static void Initialize(SyncedConfiguration synced) { CoreWoodSettings.Initialize(synced); CoreWoodSettings.Enabled.SettingChanged += delegate { CoreWoodHammer.Refresh(); }; AddWords(); } private static void AddWords() { Language.Add("ebp_corewood_wall", "Core wood wall"); Language.Add("ebp_corewood_wall_desc", "Core wood logs laid between two posts. Snaps like the stakewall."); Language.Add("ebp_corewood_wall_large", "Large core wood wall"); Language.Add("ebp_corewood_wall_large_desc", "A core wood wall twice as wide, in one piece."); Language.Add("ebp_corewood_door", "Core wood door"); Language.Add("ebp_corewood_door_desc", "A core wood gate the width of a core wood wall. Opens away from you."); Language.Add("ebp_corewood_door_double", "Core wood double door"); Language.Add("ebp_corewood_door_double_desc", "Two core wood gates that open together, the width of a large core wood wall."); } } public sealed class CoreWoodPiece { public string Prefab { get; } public string Source { get; } public string Bundle { get; } public string Word { get; } public int Cost { get; } public int Width { get; } public GameObject Built { get; set; } public bool IsDoor => Source == "wood_gate"; public string NameToken => "$ebp_" + Word; public string DescriptionToken => "$ebp_" + Word + "_desc"; public CoreWoodPiece(string prefab, string source, string bundle, string word, int cost, int width) { Prefab = prefab; Source = source; Bundle = bundle; Word = word; Cost = cost; Width = width; } } public static class CoreWoodPieces { public const string Wall = "stake_wall"; public const string Gate = "wood_gate"; public static readonly CoreWoodPiece[] All = new CoreWoodPiece[4] { new CoreWoodPiece("EBP_CoreWoodWall", "stake_wall", "va_corewoodwall_v002", "corewood_wall", 4, 1), new CoreWoodPiece("EBP_CoreWoodWallLarge", "stake_wall", "va_corewoodwall_double_v003", "corewood_wall_large", 8, 2), new CoreWoodPiece("EBP_CoreWoodDoor", "wood_gate", "va_corewoodgate_single_v002", "corewood_door", 6, 1), new CoreWoodPiece("EBP_CoreWoodDoubleDoor", "wood_gate", "va_corewoodgate_double_v002", "corewood_door_double", 12, 2) }; } [HarmonyPatch(typeof(ZNetScene), "Awake")] public static class CoreWoodPrefabs { [HarmonyPrefix] private static void Prefix(ZNetScene __instance) { try { AddTo(__instance.m_prefabs); } catch (Exception arg) { Plugin.Log.LogError((object)$"EliteBuildingPieces: the core wood pieces could not be made: {arg}"); } } private static void AddTo(List<GameObject> prefabs) { Material val = null; CoreWoodPiece[] all = CoreWoodPieces.All; foreach (CoreWoodPiece coreWoodPiece in all) { if ((Object)(object)coreWoodPiece.Built == (Object)null) { coreWoodPiece.Built = Build(prefabs, coreWoodPiece, val ?? (val = CoreWoodModel.LogWall(prefabs))); } if ((Object)(object)coreWoodPiece.Built != (Object)null && !prefabs.Contains(coreWoodPiece.Built)) { prefabs.Add(coreWoodPiece.Built); } } } private static GameObject Build(List<GameObject> prefabs, CoreWoodPiece piece, Material wood) { GameObject val = prefabs.Find((GameObject p) => (Object)(object)p != (Object)null && ((Object)p).name == piece.Source); if ((Object)(object)val == (Object)null || (Object)(object)val.GetComponent<Piece>() == (Object)null) { Plugin.Log.LogWarning((object)("EliteBuildingPieces: the game's " + piece.Source + " was not found: there is no " + piece.Prefab)); return null; } GameObject obj = PrefabBench.Copy(val, piece.Prefab); if (!CoreWoodModel.Wear(obj, piece, wood)) { Plugin.Log.LogWarning((object)("EliteBuildingPieces: " + piece.Prefab + " keeps the look of the game's " + piece.Source)); } Configure(obj, piece); return obj; } private static void Configure(GameObject copy, CoreWoodPiece piece) { Piece component = copy.GetComponent<Piece>(); component.m_name = piece.NameToken; component.m_description = piece.DescriptionToken; component.m_enabled = true; Door component2 = copy.GetComponent<Door>(); if ((Object)(object)component2 != (Object)null) { component2.m_name = piece.NameToken; } WearNTear component3 = copy.GetComponent<WearNTear>(); if ((Object)(object)component3 != (Object)null) { component3.m_health *= (float)piece.Width; } } } public static class CoreWoodSnaps { private const string Anchor = "snap_"; private const string Brace = "col_box_wall_01"; public static void Lift(Transform root, Transform placed) { //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_006b: Unknown result type (might be due to invalid IL or missing references) int num = 0; int num2 = 0; foreach (Transform item in Anchors(placed)) { Vector3 val = root.InverseTransformPoint(item.position); item.SetParent(root, false); item.localPosition = val; item.localRotation = Quaternion.identity; ((Component)item).gameObject.tag = "snappoint"; ((Component)item).gameObject.SetActive(false); ((Object)item).name = ((val.y < 1f) ? $"$hud_snappoint_bottom {++num}" : $"$hud_snappoint_mid {++num2}"); } Transform val2 = GameMaterials.Find(placed, "col_box_wall_01"); if ((Object)(object)val2 != (Object)null) { ((Component)val2).gameObject.tag = "leaky"; } } private static List<Transform> Anchors(Transform placed) { List<Transform> list = new List<Transform>(); Transform[] componentsInChildren = ((Component)placed).GetComponentsInChildren<Transform>(true); foreach (Transform val in componentsInChildren) { if (((Object)val).name.StartsWith("snap_")) { list.Add(val); } } list.Sort((Transform a, Transform b) => string.CompareOrdinal(((Object)a).name, ((Object)b).name)); return list; } } public sealed class GateSwing : MonoBehaviour { private const float OpenAngle = 100f; private const float Speed = 142.85715f; private Transform[] leaves; private float[] turns; private float angle; private float target; private bool shown; private void Awake() { ((Behaviour)this).enabled = false; } public void Follow(int state) { Find(); target = (float)Mathf.Clamp(state, -1, 1) * 100f; if (!shown) { shown = true; Turn(angle = target); } ((Behaviour)this).enabled = angle != target; } private void Update() { angle = Mathf.MoveTowards(angle, target, 142.85715f * Time.deltaTime); Turn(angle); if (angle == target) { ((Behaviour)this).enabled = false; } } private void Turn(float degrees) { //IL_0020: Unknown result type (might be due to invalid IL or missing references) for (int i = 0; i < leaves.Length; i++) { leaves[i].localRotation = Quaternion.Euler(0f, degrees * turns[i], 0f); } } private void Find() { if (leaves != null) { return; } List<Transform> list = new List<Transform>(); List<float> list2 = new List<float>(); Transform[] componentsInChildren = ((Component)this).GetComponentsInChildren<Transform>(true); foreach (Transform val in componentsInChildren) { if (((Object)val).name == "door_left" || ((Object)val).name == "door_right") { list.Add(val); list2.Add((((Object)val).name == "door_left") ? (-1f) : 1f); } } leaves = list.ToArray(); turns = list2.ToArray(); } } [HarmonyPatch(typeof(Door), "SetState")] public static class GateSwingPatch { [HarmonyPostfix] private static void Postfix(Door __instance, int state) { GateSwing component = ((Component)__instance).GetComponent<GateSwing>(); if ((Object)(object)component != (Object)null) { component.Follow(state); } } } [HarmonyPatch(typeof(Player), "IsOverlappingOtherPiece")] public static class WallLine { private const float Course = 0.5f; private const float Depth = 0.2f; private const float Touch = 0.1f; [HarmonyPostfix] private static void Postfix(Vector3 p, Quaternion rotation, string pieceName, List<Piece> pieces, ref bool __result) { //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Unknown result type (might be due to invalid IL or missing references) float num = (__result ? 0f : Width(pieceName)); if (num <= 0f || pieces == null) { return; } foreach (Piece piece in pieces) { if ((Object)(object)piece != (Object)null && Inside(p, rotation, num, piece)) { __result = true; break; } } } private static bool Inside(Vector3 p, Quaternion rotation, float width, Piece other) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0053: Unknown result type (might be due to invalid IL or missing references) //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_0059: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0083: Unknown result type (might be due to invalid IL or missing references) float num = Width(((Object)((Component)other).gameObject).name); if (num <= 0f || Mathf.Abs(Vector3.Dot(rotation * Vector3.right, ((Component)other).transform.right)) < 0.98f) { return false; } Vector3 val = Quaternion.Inverse(rotation) * (((Component)other).transform.position - p); if (Mathf.Abs(val.y) < 0.5f && Mathf.Abs(val.z) < 0.2f) { return Mathf.Abs(val.x) < (width + num) / 2f - 0.1f; } return false; } private static float Width(string name) { int num = name.IndexOf("(Clone)", StringComparison.Ordinal); if (num >= 0) { name = name.Substring(0, num); } if (name == "stake_wall") { return 2f; } CoreWoodPiece[] all = CoreWoodPieces.All; foreach (CoreWoodPiece coreWoodPiece in all) { if (coreWoodPiece.Prefab == name) { return 2f * (float)coreWoodPiece.Width; } } return 0f; } } } namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)] internal sealed class IgnoresAccessChecksToAttribute : Attribute { public IgnoresAccessChecksToAttribute(string assemblyName) { } } [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; } } } namespace SyncedConfig { public sealed class SyncedConfiguration { public global::Charter.Charter Sync { get; } public ConfigFile Config { get; } public ManualLogSource Log { get; } public YamlFileHub Yaml { get; } public YamlEditorWindow YamlEditor { get; } public IReadOnlyList<string> SearchPaths { get; } public IDisposable? ConfigWatcher { get; private set; } public bool IsLocked { get { if (Sync.IsBound) { return !Sync.MayAmend; } return false; } } public bool IsAdmin => Sync.IsSteward; public bool IsAuthor => Sync.IsAuthor; public SyncedConfiguration(BaseUnityPlugin plugin, ManualLogSource log, string title, string version, string? oldestAccepted = null, bool mandatory = true) { Config = plugin.Config; ConfigReloader.HoldSaves(Config); Log = log; string gUID = plugin.Info.Metadata.GUID; Sync = new global::Charter.Charter(gUID, title, version, oldestAccepted, mandatory); SearchPaths = new string[2] { Path.GetDirectoryName(Config.ConfigFilePath) ?? Paths.ConfigPath, Path.GetDirectoryName(((object)plugin).GetType().Assembly.Location) ?? Paths.PluginPath }; Yaml = new YamlFileHub(title, Log, SearchPaths, Sync); YamlEditor = new YamlEditorWindow(Yaml, title + " YAML Editor"); YamlEditorHost.Add(((Component)plugin).gameObject, YamlEditor); } public ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, string description, bool synced = true, AcceptableValueBase? acceptableValues = null, params object[] tags) { //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Expected O, but got Unknown return Bind(section, key, defaultValue, new ConfigDescription(description, acceptableValues, tags), synced); } public ConfigEntry<T> Bind<T>(string section, string key, T defaultValue, ConfigDescription description, bool synced = true) { ConfigEntry<T> val = Config.Bind<T>(section, key, defaultValue, description); Sync.Clause<T>(val, !synced); return val; } public Clause<T> Add<T>(ConfigEntry<T> entry, bool synced = true) { return Sync.Clause<T>(entry, !synced); } public ConfigEntry<bool> BindLocking(string section, string key, bool defaultValue, string description) { ConfigEntry<bool> val = Config.Bind<bool>(section, key, defaultValue, description); Sync.Binding(val); return val; } public YamlFileSet AddYaml(YamlFileSet set) { return Yaml.Register(set); } public void Finish(Harmony harmony, int yamlApplyPriority = 400) { global::Charter.Charter.Install(harmony); ConfigWatcher = ConfigReloader.Setup(Config, Log); if (Yaml.Sets.Count > 0) { Yaml.HookGame(harmony, yamlApplyPriority); } } public static Func<byte[]?> EmbeddedResource(Assembly assembly, string resourceName) { return delegate { using Stream stream = assembly.GetManifestResourceStream(resourceName); if (stream != null) { using MemoryStream memoryStream = new MemoryStream(); stream.CopyTo(memoryStream); return memoryStream.ToArray(); } return (byte[]?)null; }; } } } namespace YamlConfig { internal sealed class YamlEditorHost : MonoBehaviour { private YamlEditorWindow? window; private bool laidOut; public static YamlEditorHost Add(GameObject on, YamlEditorWindow window) { YamlEditorHost yamlEditorHost = on.AddComponent<YamlEditorHost>(); yamlEditorHost.window = window; window.Host = yamlEditorHost; ((Behaviour)yamlEditorHost).enabled = window.IsOpen; return yamlEditorHost; } private void Update() { window?.Update(); } private void LateUpdate() { window?.Update(); } private void OnDisable() { laidOut = false; } private void OnGUI() { //IL_000d: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Invalid comparison between Unknown and I4 if (laidOut || (int)Event.current.type == 8) { laidOut = true; window?.Draw(); } } } public sealed class YamlEditorWindow { private const float ValidationHeight = 100f; private readonly YamlFileHub hub; private readonly string windowTitle; private readonly List<string> paths = new List<string>(); private readonly List<string> texts = new List<string>(); private readonly List<bool> expanded = new List<bool>(); private readonly List<Vector2> scrolls = new List<Vector2>(); private readonly List<string> errors = new List<string>(); private readonly List<string> warnings = new List<string>(); private Vector2 validationScroll; private YamlFileSet? set; private Texture2D? background; private GUIStyle? errorStyle; public Func<string, string> Translate { get; set; } = (string caption) => caption; public bool IsOpen => set != null; internal YamlEditorHost? Host { get; set; } private Texture2D Background { get { //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_001b: Expected O, but got Unknown //IL_0037: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)background == (Object)null) { background = new Texture2D(1, 1); background.SetPixel(0, 0, new Color(0f, 0f, 0f, 0.9f)); background.Apply(); } return background; } } private GUIStyle ErrorStyle { get { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_0020: 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_002c: Expected O, but got Unknown //IL_0031: Expected O, but got Unknown GUIStyle obj = errorStyle; if (obj == null) { GUIStyle val = new GUIStyle(GUI.skin.label); val.normal.textColor = Color.red; GUIStyle val2 = val; errorStyle = val; obj = val2; } return obj; } } public YamlEditorWindow(YamlFileHub hub, string windowTitle) { this.hub = hub; this.windowTitle = windowTitle; } public void Open(YamlFileSet set) { //IL_007e: Unknown result type (might be due to invalid IL or missing references) this.set = set; paths.Clear(); texts.Clear(); expanded.Clear(); scrolls.Clear(); foreach (KeyValuePair<string, string> file in set.Files) { paths.Add(file.Key); texts.Add(file.Value); expanded.Add(item: true); scrolls.Add(Vector2.zero); } Validate(); FollowHost(); } public void Close() { set = null; FollowHost(); } private void FollowHost() { if ((Object)(object)Host != (Object)null) { ((Behaviour)Host).enabled = IsOpen; } } public void DrawButtons() { foreach (YamlFileSet item in hub.Sets.Where((YamlFileSet s) => s.Files.Count > 0)) { if (GUILayout.Button(Translate(item.EditorLabel()), (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandWidth(true) })) { Open(item); } } } public void Update() { if (IsOpen) { Cursor.lockState = (CursorLockMode)0; Cursor.visible = true; } } public void OnGUI() { if ((Object)(object)Host == (Object)null) { Draw(); } } internal void Draw() { //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) if (set != null) { HandleEditingKeys(); GUI.DrawTexture(new Rect(0f, 0f, (float)Screen.width, (float)Screen.height), (Texture)(object)Background); GUILayout.BeginArea(new Rect(20f, 20f, (float)(Screen.width - 40), (float)(Screen.height - 40))); GUILayout.Label(Translate(windowTitle), Array.Empty<GUILayoutOption>()); DrawTopRow(); for (int i = 0; i < texts.Count; i++) { DrawFile(i); } DrawValidation(); GUILayout.EndArea(); } } private void DrawTopRow() { GUILayout.BeginHorizontal(Array.Empty<GUILayoutOption>()); GUI.enabled = hub.IsAuthor && errors.Count == 0; if (GUILayout.Button(Translate("Save and apply"), Array.Empty<GUILayoutOption>())) { Submit(saveToDisk: true); } if (GUILayout.Button(Translate("Apply without saving"), Array.Empty<GUILayoutOption>())) { Submit(saveToDisk: false); } GUI.enabled = true; if (GUILayout.Button(Translate("Discard"), Array.Empty<GUILayoutOption>())) { Close(); } GUILayout.EndHorizontal(); } private void Submit(bool saveToDisk) { if (set != null) { Dictionary<string, string> dictionary = new Dictionary<string, string>(); for (int i = 0; i < paths.Count; i++) { dictionary[paths[i]] = texts[i]; } hub.Replace(set, dictionary, saveToDisk); Close(); } } private void DrawFile(int index) { //IL_005e: Unknown result type (might be due to invalid IL or missing references) //IL_0072: Unknown result type (might be due to invalid IL or missing references) if (texts.Count > 1) { expanded[index] = GUILayout.Toggle(expanded[index], Path.GetFileName(paths[index]), Array.Empty<GUILayoutOption>()); if (!expanded[index]) { return; } } scrolls[index] = GUILayout.BeginScrollView(scrolls[index], (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUI.SetNextControlName(ControlName(index)); string text = GUILayout.TextArea(texts[index], (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.ExpandHeight(true) }); GUILayout.EndScrollView(); if (text != texts[index]) { texts[index] = text; Validate(); } } private void DrawValidation() { //IL_0002: Unknown result type (might be due to invalid IL or missing references) //IL_001a: Unknown result type (might be due to invalid IL or missing references) //IL_001f: Unknown result type (might be due to invalid IL or missing references) validationScroll = GUILayout.BeginScrollView(validationScroll, (GUILayoutOption[])(object)new GUILayoutOption[1] { GUILayout.Height(100f) }); if (errors.Count == 0 && warnings.Count == 0) { GUILayout.Label(Translate("Configuration is valid."), Array.Empty<GUILayoutOption>()); } foreach (string error in errors) { GUILayout.Label(error, ErrorStyle, Array.Empty<GUILayoutOption>()); } foreach (string warning in warnings) { GUILayout.Label(warning, Array.Empty<GUILayoutOption>()); } GUILayout.EndScrollView(); } private void Validate() { errors.Clear(); warnings.Clear(); if (set != null) { YamlModel yamlModel = set.CreateModel(); yamlModel.LoadAll(paths.Select((string path, int i) => new KeyValuePair<string, string>(path, texts[i]))); errors.AddRange(yamlModel.Errors); warnings.AddRange(yamlModel.Warnings); } } private void HandleEditingKeys() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 //IL_0011: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Invalid comparison between Unknown and I4 //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Invalid comparison between Unknown and I4 //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Invalid comparison between Unknown and I4 //IL_0072: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Expected O, but got Unknown Event current = Event.current; if ((int)current.type == 4) { bool flag = (int)current.keyCode == 9 || current.character == '\t'; bool flag2 = (int)current.keyCode == 13 || (int)current.keyCode == 271 || current.character == '\n'; int num = FocusedFile(); if (num >= 0 && (flag || flag2)) { TextEditor val = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl); val.ReplaceSelection(flag ? " " : ("\n" + IndentationOfLineAt(val.text, val.cursorIndex))); texts[num] = val.text; current.Use(); Validate(); } } } private int FocusedFile() { string nameOfFocusedControl = GUI.GetNameOfFocusedControl(); for (int i = 0; i < texts.Count; i++) { if (nameOfFocusedControl == ControlName(i)) { return i; } } return -1; } private static string IndentationOfLineAt(string text, int position) { int num = Math.Max(0, Math.Min(position, text.Length)); while (num > 0 && text[num - 1] != '\n') { num--; } int i; for (i = num; i < text.Length && text[i] == ' '; i++) { } return text.Substring(num, i - num); } private static string ControlName(int index) { return "YamlEditorWindow.File" + index; } } public sealed class YamlFileHub { private readonly string modName; private readonly ManualLogSource log; private readonly global::Charter.Charter? charter; private readonly List<YamlFileSet> sets = new List<YamlFileSet>(); private readonly YamlFileStore store; private readonly YamlFileWatcher watcher; private bool loaded; private (YamlFileSet Set, YamlModel Model)? built; public IReadOnlyList<YamlFileSet> Sets => sets; public bool IsAuthor => charter?.IsAuthor ?? true; public bool IsLocked { get { if (charter != null && charter.IsBound) { return !charter.MayAmend; } return false; } } public bool SuppressWriteBack { get; set; } public Func<bool> CanApply { get; set; } = () => true; public event Action<YamlFileSet>? Applied; public YamlFileHub(string modName, ManualLogSource log, IEnumerable<string> searchFolders, global::Charter.Charter? charter) { this.modName = modName; this.log = log; this.charter = charter; store = new YamlFileStore(modName, log, searchFolders); watcher = new YamlFileWatcher(store, sets, ReloadFromDisk, log, modName); } public YamlFileSet Register(YamlFileSet set) { if (sets.Any((YamlFileSet s) => string.Equals(s.SyncKey, set.SyncKey, StringComparison.OrdinalIgnoreCase))) { throw new ArgumentException(modName + ": a YAML file set with sync key '" + set.SyncKey + "' is already registered"); } sets.Add(set); if (charter != null) { Article<List<string>> channel = new Article<List<string>>(charter, set.SyncKey, new List<string>()); set.Channel = channel; channel.Changed += delegate { Receive(set, YamlFileList.Unflatten(channel.Value)); }; } return set; } public void LoadAll() { loaded = true; foreach (YamlFileSet set in sets) { LoadSet(set); } watcher.Start(); } public void ApplyAll() { if (!CanApply()) { log.LogDebug((object)(modName + ": YAML apply skipped, the game is not ready")); return; } foreach (YamlFileSet set in sets) { Apply(set); } } public bool TryBuild(YamlFileSet set, IReadOnlyDictionary<string, string> files, out YamlModel model, string context) { model = set.CreateModel(); model.LoadAll(files); foreach (string warning in model.Warnings) { log.LogWarning((object)(modName + ": " + warning)); } if (model.Errors.Count == 0) { return true; } log.LogError((object)$"{modName}: {set.MainFileName} ({context}) has {model.Errors.Count} error(s), the previous configuration stays:"); foreach (string error in model.Errors) { log.LogError((object)(modName + ": " + error)); } return false; } public void Replace(YamlFileSet set, IReadOnlyDictionary<string, string> files, bool saveToDisk) { if (TryBuild(set, files, out YamlModel model, "editor")) { Publish(set, files, model, saveToDisk); } } public void HookGame(Harmony harmony, int applyPriority) { YamlGameHooks.Install(this, harmony, applyPriority); } internal void LoadFromHook() { if (loaded) { return; } try { LoadAll(); } catch (Exception arg) { log.LogError((object)$"{modName}: loading YAML files failed: {arg}"); } } internal void ApplyFromHook() { try { ApplyAll(); } catch (Exception arg) { log.LogError((object)$"{modName}: applying YAML files failed: {arg}"); } } private void LoadSet(YamlFileSet set) { Dictionary<string, string> dictionary = watcher.ReadOrCreate(set); YamlModel model; if (dictionary.Count == 0) { if (set.Enabled()) { log.LogWarning((object)(modName + ": no " + set.FilePattern + " found in " + string.Join(" or ", store.SearchFolders))); } } else if (TryBuild(set, dictionary, out model, "startup")) { Publish(set, dictionary, model, writeBack: false); } } private void ReloadFromDisk(YamlFileSet set) { Dictionary<string, string> dictionary = watcher.Read(set); YamlModel model; if (dictionary.Count == 0) { log.LogWarning((object)(modName + ": every " + set.FilePattern + " file is gone, keeping the loaded configuration")); } else if (TryBuild(set, dictionary, out model, "reload")) { Publish(set, dictionary, model, writeBack: false); log.LogInfo((object)(modName + ": " + set.MainFileName + " reloaded" + (IsAuthor ? "" : ", not applied, remote configuration active"))); } } private void Publish(YamlFileSet set, IReadOnlyDictionary<string, string> files, YamlModel model, bool writeBack) { bool suppressWriteBack = SuppressWriteBack; (YamlFileSet, YamlModel)? tuple = built; SuppressWriteBack = suppressWriteBack || !writeBack; built = (set, model); try { if (set.Channel == null) { Receive(set, files); } else { set.Channel.Assign(YamlFileList.Flatten(files)); } } finally { SuppressWriteBack = suppressWriteBack; built = tuple; } } private void Receive(YamlFileSet set, IReadOnlyDictionary<string, string> files) { (YamlFileSet, YamlModel)? tuple = built; YamlModel? yamlModel; if (tuple.HasValue) { (YamlFileSet, YamlModel) valueOrDefault = tuple.GetValueOrDefault(); if (valueOrDefault.Item1 == set) { yamlModel = valueOrDefault.Item2; goto IL_0031; } } yamlModel = BuildReceived(set, files); goto IL_0031; IL_0031: YamlModel yamlModel2 = yamlModel; if (yamlModel2 != null) { set.Files = files; set.Current = yamlModel2; if (CanApply()) { Apply(set); } if (IsAuthor && !SuppressWriteBack) { store.WriteAll(files); watcher.TakeSnapshot(set); } } } private YamlModel? BuildReceived(YamlFileSet set, IReadOnlyDictionary<string, string> files) { if (TryBuild(set, files, out YamlModel model, "received")) { return model; } foreach (KeyValuePair<string, string> file in files) { log.LogWarning((object)(modName + ": content of " + file.Key + ":\n" + file.Value)); } return null; } private void Apply(YamlFileSet set) { if (set.Current == null || !set.Enabled()) { return; } try { set.Apply(set.Current); this.Applied?.Invoke(set); } catch (Exception arg) { log.LogError((object)$"{modName}: applying {set.MainFileName} failed: {arg}"); } } } internal static class YamlFileList { public static List<string> Flatten(IReadOnlyDictionary<string, string> files) { List<string> list = new List<string>(files.Count * 2); foreach (KeyValuePair<string, string> file in files) { list.Add(file.Key); list.Add(file.Value); } return list; } public static Dictionary<string, string> Unflatten(List<string>? list) { Dictionary<string, string> dictionary = new Dictionary<string, string>(); int num = 0; while (list != null && num + 1 < list.Count) { dictionary[list[num]] = list[num + 1]; num += 2; } return dictionary; } } public sealed class YamlFileSet { internal int ReloadPending; private Regex? nameFilter; public string FilePattern { get; } public string MainFileName { get; } public string SyncKey { get; } public Func<YamlModel> CreateModel { get; } public Action<YamlModel> Apply { get; } public Func<bool> Enabled { get; set; } = () => true; public Func<byte[]?>? DefaultContent { get; set; } public Func<string> EditorLabel { get; set; } public IReadOnlyDictionary<string, string> Files { get; internal set; } = new Dictionary<string, string>(); public YamlModel? Current { get; internal set; } internal Article<List<string>>? Channel { get; set; } internal Dictionary<string, DateTime> Snapshot { get; } = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase); internal Regex NameFilter => nameFilter ?? (nameFilter = new Regex("^" + Regex.Escape(FilePattern).Replace("\\*", ".*") + "$", RegexOptions.IgnoreCase)); public YamlFileSet(string filePattern, string syncKey, Func<YamlModel> createModel, Action<YamlModel> apply) { FilePattern = filePattern; MainFileName = filePattern.Replace("*", ""); SyncKey = syncKey; CreateModel = createModel; Apply = apply; EditorLabel = () => "Edit " + MainFileName; } } internal sealed class YamlFileStore { private readonly string modName; private readonly ManualLogSource log; private readonly List<string> searchFolders; public IReadOnlyList<string> SearchFolders => searchFolders; public YamlFileStore(string modName, ManualLogSource log, IEnumerable<string> searchFolders) { this.modName = modName; this.log = log; this.searchFolders = searchFolders.ToList(); } public List<string> Discover(YamlFileSet set) { Regex nameFilter = set.NameFilter; List<string> list = new List<string>(); foreach (string item in searchFolders.Where(Directory.Exists)) { list.AddRange(from p in Directory.GetFiles(item, set.FilePattern) where nameFilter.IsMatch(Path.GetFileName(p)) select p); } return list.OrderBy((string p) => (!string.Equals(Path.GetFileName(p), set.MainFileName, StringComparison.OrdinalIgnoreCase)) ? 1 : 0).ThenBy<string, string>(Path.GetFileName, StringComparer.OrdinalIgnoreCase).ToList(); } public Dictionary<string, string> Read(IEnumerable<string> paths) { Dictionary<string, string> dictionary = new Dictionary<string, string>(); foreach (string path in paths) { try { dictionary[path] = File.ReadAllText(path); } catch (Exception ex) { log.LogError((object)(modName + ": reading " + path + " failed: " + ex.Message)); } } return dictionary; } public Dictionary<string, string> ReadOrCreate(YamlFileSet set) { Dictionary<string, string> dictionary = Read(Discover(set)); if (dictionary.Count <= 0) { return WriteDefault(set); } return dictionary; } public Dictionary<string, DateTime> WriteTimes(YamlFileSet set) { Dictionary<string, DateTime> dictionary = new Dictionary<string, DateTime>(StringComparer.OrdinalIgnoreCase); foreach (string item in Discover(set)) { dictionary[item] = File.GetLastWriteTimeUtc(item); } return dictionary; } public void WriteAll(IReadOnlyDictionary<string, string> files) { foreach (KeyValuePair<string, string> file in files) { WriteAtomic(file.Key, file.Value); } } private Dictionary<string, string> WriteDefault(YamlFileSet set) { byte[] array = set.DefaultContent?.Invoke(); if (array == null || searchFolders.Count == 0) { return new Dictionary<string, string>(); } string text = Path.Combine(searchFolders[0], set.MainFileName); try { Directory.CreateDirectory(searchFolders[0]); File.WriteAllBytes(text, array); log.LogInfo((object)(modName + ": wrote default " + text)); } catch (Exception ex) { log.LogError((object)(modName + ": writing default " + text + " failed: " + ex.Message)); return new Dictionary<string, string>(); } return Read(new string[1] { text }); } private void WriteAtomic(string path, string content) { try { if (!File.Exists(path) || !(File.ReadAllText(path) == content)) { ReplaceThroughTempFile(path, content); log.LogInfo((object)(modName + ": wrote " + path)); } } catch (Exception ex) { log.LogError((object)(modName + ": writing " + path + " failed: " + ex.Message)); } } private static void ReplaceThroughTempFile(string path, string content) { string text = path + ".tmp"; File.WriteAllText(text, content, new UTF8Encoding(encoderShouldEmitUTF8Identifier: false)); if (File.Exists(path)) { File.Replace(text, path, null); } else { File.Move(text, path); } } } internal sealed class YamlFileWatcher { private const int PollMilliseconds = 5000; private readonly YamlFileStore store; private readonly IReadOnlyList<YamlFileSet> sets; private readonly Action<YamlFileSet> reload; private readonly ManualLogSource log; private readonly string modName; private Timer? timer; public YamlFileWatcher(YamlFileStore store, IReadOnlyList<YamlFileSet> sets, Action<YamlFileSet> reload, ManualLogSource log, string modName) { this.store = store; this.sets = sets; this.reload = reload; this.log = log; this.modName = modName; } public void Start() { if (timer == null) { timer = new Timer(delegate { Poll(); }, null, 5000, 5000); } } public Dictionary<string, string> ReadOrCreate(YamlFileSet set) { TakeSnapshot(set); bool flag; lock (set.Snapshot) { flag = set.Snapshot.Count > 0; } Dictionary<string, string> dictionary = store.ReadOrCreate(set); if (!flag && dictionary.Count > 0) { TakeSnapshot(set); } return dictionary; } public Dictionary<string, string> Read(YamlFileSet set) { TakeSnapshot(set); return store.Read(store.Discover(set)); } public void TakeSnapshot(YamlFileSet set) { Dictionary<string, DateTime> dictionary = store.WriteTimes(set); lock (set.Snapshot) { set.Snapshot.Clear(); foreach (KeyValuePair<string, DateTime> item in dictionary) { set.Snapshot[item.Key] = item.Value; } } } private void Poll() { try { foreach (YamlFileSet set in sets.Where(HasChanged)) { if (Interlocked.CompareExchange(ref set.ReloadPending, 1, 0) == 0) { ThreadingHelper.SynchronizingObject.BeginInvoke((Action)delegate { Reload(set); }, null); } } } catch (Exception ex) { log.LogError((object)(modName + ": watching YAML files failed: " + ex.Message)); } } private bool HasChanged(YamlFileSet set) { Dictionary<string, DateTime> dictionary = store.WriteTimes(set); lock (set.Snapshot) { DateTime value; return dictionary.Count != set.Snapshot.Count || dictionary.Any((KeyValuePair<string, DateTime> pair) => !set.Snapshot.TryGetValue(pair.Key, out value) || value != pair.Value); } } private void Reload(YamlFileSet set) { try { reload(set); } catch (Exception arg) { log.LogError((object)$"{modName}: reloading {set.MainFileName} failed: {arg}"); } finally { Interlocked.Exchange(ref set.ReloadPending, 0); } } } internal static class YamlGameHooks { private static class Patches { public static void AfterMenuStart() { LoadHooked(); } public static void AfterNetworkStart(ZNet __instance) { if (__instance.IsServer() && __instance.IsDedicated()) { LoadHooked(); } } public static void AfterRespawnRequest(Game __instance) { object obj = firstSpawnField?.GetValue(__instance); if (obj is bool && (bool)obj) { ApplyHooked(); } } } private static readonly List<YamlFileHub> hooked = new List<YamlFileHub>(); private static readonly FieldInfo? firstSpawnField = AccessTools.Field(typeof(Game), "m_firstSpawn"); private static bool patched; public static void Install(YamlFileHub hub, Harmony harmony, int applyPriority) { //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_005b: Expected O, but got Unknown //IL_0084: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Expected O, but got Unknown //IL_00a2: Unknown result type (might be due to invalid IL or missing references) //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00af: Expected O, but got Unknown if (!hooked.Contains(hub)) { hooked.Add(hub); } if (!patched) { patched = true; harmony.Patch((MethodBase)AccessTools.Method(typeof(FejdStartup), "Start", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "AfterMenuStart", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); harmony.Patch((MethodBase)AccessTools.Method(typeof(ZNet), "Awake", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "AfterNetworkStart", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); HarmonyMethod val = new HarmonyMethod(typeof(Patches), "AfterRespawnRequest", (Type[])null) { priority = applyPriority }; harmony.Patch((MethodBase)AccessTools.Method(typeof(Game), "RequestRespawn", (Type[])null, (Type[])null), (HarmonyMethod)null, val, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null); } } private static void LoadHooked() { foreach (YamlFileHub item in hooked) { item.LoadFromHook(); } } private static void ApplyHooked() { foreach (YamlFileHub item in hooked) { item.ApplyFromHook(); } } } internal sealed class YamlGroupTable { private readonly Dictionary<string, List<string>> members = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase); private readonly Dictionary<string, List<string>> parents = new Dictionary<string, List<string>>(StringComparer.OrdinalIgnoreCase); public IReadOnlyCollection<string> Names => members.Keys; public bool IsGroup(string name) { return members.ContainsKey(name); } public void Add(string group, IEnumerable<string> names) { if (!members.TryGetValue(group, out List<string> value)) { value = new List<string>(); members.Add(group, value); } foreach (string name in names) { value.Add(name); if (!parents.TryGetValue(name, out List<string> value2)) { value2 = new List<string>(); parents.Add(name, value2); } value2.Add(group); } } public IReadOnlyList<string> Resolve(string name) { List<string> list = new List<string>(); HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); Queue<string> queue = new Queue<string>(); queue.Enqueue(name); while (queue.Count > 0) { if (!parents.TryGetValue(queue.Dequeue(), out List<string> value)) { continue; } foreach (string item in value) { if (hashSet.Add(item)) { list.Add(item); queue.Enqueue(item); } } } return list; } public IReadOnlyList<string> Expand(string group) { List<string> result = new List<string>(); HashSet<string> seen = new HashSet<string>(StringComparer.OrdinalIgnoreCase); Expand(group, seen, result); return result; } private void Expand(string name, HashSet<string> seen, List<string> result) { if (!seen.Add(name)) { return; } if (!members.TryGetValue(name, out List<string> value)) { result.Add(name); return; } foreach (string item in value) { Expand(item, seen, result); } } } public abstract class YamlModel { private static readonly IDeserializer Parser = new DeserializerBuilder().Build(); private string? currentFile; public List<string> Errors { get; } = new List<string>(); public List<string> Warnings { get; } = new List<string>(); protected YamlGroupTable Groups { get; } = new YamlGroupTable(); public void Load(string yamlText) { ReadText(yamlText); Verify(); } public void LoadAll(IEnumerable<KeyValuePair<string, string>> files) { foreach (KeyValuePair<string, string> file in files) { currentFile = Path.GetFileName(file.Key); ReadText(file.Value); } currentFile = null; Verify(); } public IReadOnlyList<string> ResolveGroups(string name) { return Groups.Resolve(name); } protected abstract void Read(YamlNode root); protected virtual void Verify() { } protected void ReadGroups(YamlNode root, string key = "groups") { foreach (KeyValuePair<string, YamlNode> entry in root.Get(key).Entries) { if (entry.Value.TryStringList(out List<string> items)) { Groups.Add(entry.Key, items); } } } internal void AddError(string message) { Errors.Add(WithFile(message)); } internal void AddWarning(string message) { Warnings.Add(WithFile(message)); } private string WithFile(string message) { if (currentFile != null) { return currentFile + ": " + message; } return message; } private void ReadText(string yamlText) { if (TryParse(yamlText, out object graph) && graph != null) { if (!(graph is IDictionary<object, object>)) { AddError("the file must be a map of keys at the top level, not " + ((graph is IList<object>) ? "a list" : "a single value")); return; } YamlNode yamlNode = new YamlNode(this, graph, ""); Read(yamlNode); yamlNode.WarnUnknownKeys(); } } private bool TryParse(string yamlText, out object? graph) { try { graph = Parser.Deserialize<object>(yamlText.TrimStart(new char[1] { '\ufeff' })); return true; } catch (Exception ex) { AddError("invalid YAML: " + ex.Message + ((ex.InnerException == null) ? "" : (" (" + ex.InnerException.Message + ")"))); graph = null; return false; } } } internal enum YamlNodeKind { Scalar, List, Map, Null, Missing } internal sealed class YamlNode { private readonly YamlModel model; private readonly object? value; private List<KeyValuePair<string, YamlNode>>? mapEntries; private Dictionary<string, YamlNode>? mapLookup; private List<YamlNode>? listItems; private HashSet<string>? askedKeys; private bool visited; private bool shapeReported; public string Path { get; } public YamlNodeKind Kind { get; } public string? Text { get { if (Kind != YamlNodeKind.Scalar) { return null; } return Convert.ToString(value, CultureInfo.InvariantCulture); } } public int Count => Kind switch { YamlNodeKind.Map => MapEntries.Count, YamlNodeKind.List => ListItems.Count, _ => 0, }; public IReadOnlyList<KeyValuePair<string, YamlNode>> Entries { get { if (!ExpectShape(YamlNodeKind.Map, "a map")) { return Array.Empty<KeyValuePair<string, YamlNode>>(); } visited = true; AskedKeys.UnionWith(MapEntries.Select<KeyValuePair<string, YamlNode>, string>((KeyValuePair<string, YamlNode> e) => e.Key)); return MapEntries; } } public IReadOnlyList<YamlNode> Items { get { if (!ExpectShape(YamlNodeKind.List, "a list")) { return Array.Empty<YamlNode>(); } return ListItems; } } private HashSet<string> AskedKeys => askedKeys ?? (askedKeys = new HashSet<string>(StringComparer.OrdinalIgnoreCase)); private List<KeyValuePair<string, YamlNode>> MapEntries { get { if (mapEntries == null) { BuildMap(); } return mapEntries; } } private Dictionary<string, YamlNode> MapLookup { get { if (mapLookup == null) { BuildMap(); } return mapLookup; } } private List<YamlNode> ListItems { get { if (listItems == null) { IList<object> source = (IList<object>)value; listItems = source.Select((object item, int i) => new YamlNode(model, item, $"{Path}[{i}]")).ToList(); } return listItems; } } internal YamlNode(YamlModel model, object? value, string path, YamlNodeKind kind) { this.model = model; this.value = value; Path = path; Kind = kind; } internal YamlNode(YamlModel model, object? value, string path) : this(model, value, path, KindOf(value)) { } public YamlNode Get(string key) { string path = (string.IsNullOrEmpty(Path) ? key : (Path + "." + key)); if (!ExpectShape(YamlNodeKind.Map, "a map")) { return new YamlNode(model, null, path, YamlNodeKind.Missing); } visited = true; AskedKeys.Add(key); if (!MapLookup.TryGetValue(key, out YamlNode result)) { return new YamlNode(model, null, path, YamlNodeKind.Missing); } return result; } public bool Is(string keyword) { if (Kind == YamlNodeKind.Scalar) { return string.Equals(Text.Trim(), keyword, StringComparison.OrdinalIgnoreCase); } return false; } public void Error(string message) { model.AddError(Prefixed(message)); } public void Warn(string message) { model.AddWarning(Prefixed(message)); } public void WarnUnknownKeys() { if (Kind == YamlNodeKind.Map && visited) { List<string> list = (from e in MapEntries select e.Key into k where !AskedKeys.Contains(k) select k).ToList(); if (list.Count > 0) { Warn("unknown keys " + string.Join(", ", list)); } { foreach (KeyValuePair<string, YamlNode> mapEntry in MapEntries) { mapEntry.Value.WarnUnknownKeys(); } return; } } if (Kind == YamlNodeKind.List && listItems != null) { listItems.ForEach(delegate(YamlNode item) { item.WarnUnknownKeys(); }); } } private string Prefixed(string message) { if (!string.IsNullOrEmpty(Path)) { return Path + ": " + message; } return message; } private void BuildMap() { mapEntries = new List<KeyValuePair<string, YamlNode>>(); mapLookup = new Dictionary<string, YamlNode>(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair<object, object> item in (IDictionary<object, object>)value) { string text = Convert.ToString(item.Key, CultureInfo.InvariantCulture) ?? ""; string path = (string.IsNullOrEmpty(Path) ? text : (Path + "." + text)); YamlNode yamlNode = new YamlNode(model, item.Value, path); if (mapLookup.ContainsKey(text)) { yamlNode.Warn("duplicate key (differs only in case), ignored"); continue; } mapLookup.Add(text, yamlNode); mapEntries.Add(new KeyValuePair<string, YamlNode>(text, yamlNode)); } } private bool ExpectShape(YamlNodeKind wanted, string description) { if (Kind == wanted) { return true; } if (Kind != YamlNodeKind.Missing && !shapeReported) { shapeReported = true; Error((Kind == YamlNodeKind.Null) ? ("expected " + description + " but the key has no value") : ("expected " + description + ", found " + Describe(Kind))); } return false; } private static string Describe(YamlNodeKind kind) { return kind switch { YamlNodeKind.Scalar => "a single value", YamlNodeKind.List => "a list", YamlNodeKind.Map => "a map", _ => "nothing", }; } private static YamlNodeKind KindOf(object? value) { if (value != null) { if (!(value is IDictionary<object, object>)) { if (value is IList<object>) { return YamlNodeKind.List; } return YamlNodeKind.Scalar; } return YamlNodeKind.Map; } return YamlNodeKind.Null; } public bool TryInt(out int result) { return TryScalar<int>(out result); } public bool TryFloat(out float result) { return TryScalar<float>(out result); } public bool TryBool(out bool result) { return TryScalar<bool>(out result); } public bool TryString(out string result) { return TryScalar<string>(out result); } public bool TryEnum<T>(out T result) where T : struct, Enum { return TryScalar<T>(out result); } public bool TryList<T>(out List<T> items) { items = new List<T>(); if (!ExpectShape(YamlNodeKind.List, "a list of " + YamlScalarParser.Plural<T>())) { return false; } bool result = true; foreach (YamlNode listItem in ListItems) { if (listItem.TryScalar<T>(out var result2)) { items.Add(result2); } else { result = false; } } return result; } public bool TryStringList(out List<string> items) { if (Kind == YamlNodeKind.Scalar) { items = new List<string> { Text }; return true; } return TryList(out items); } private bool TryScalar<T>(out T result) { result = default(T); if (!ExpectShape(YamlNodeKind.Scalar, YamlScalarParser.Describe(typeof(T)))) { return false; } if (YamlScalarParser.TryParse(Text, typeof(T), out object result2)) { result = (T)result2; return true; } Error("'" + Text + "' is not " + YamlScalarParser.Describe(typeof(T))); return false; } } internal static class YamlScalarParser { private static readonly string[] TrueWords = new string[4] { "true", "yes", "on", "1" }; private static readonly string[] FalseWords = new string[4] { "false", "no", "off", "0" }; public static bool TryParse(string text, Type type, out object? result) { string text2 = text.Trim(); result = null; if (type == typeof(string)) { result = text; return true; } if (type == typeof(int)) { int result3; bool result2 = int.TryParse(text2, NumberStyles.Integer, CultureInfo.InvariantCulture, out result3); result = result3; return result2; } if (type == typeof(float)) { float result5; bool result4 = float.TryParse(text2, NumberStyles.Float, CultureInfo.InvariantCulture, out result5); result = result5; return result4; } if (!(type == typeof(bool))) { return TryParseEnum(text2, type, out result); } return TryParseBool(text2, out result); } public static string Describe(Type type) { if (type == typeof(int)) { return "an integer"; } if (type == typeof(float)) { return "a number"; } if (type == typeof(bool)) { return "true or false"; } if (!type.IsEnum) { return "text"; } return "one of " + string.Join(", ", Enum.GetNames(type)); } public static string Plural<T>() { if (!(typeof(T) == typeof(int))) { if (!(typeof(T) == typeof(float))) { if (!(typeof(T) == typeof(bool))) { if (!typeof(T).IsEnum) { return "text values"; } return "names (" + string.Join(", ", Enum.GetNames(typeof(T))) + ")"; } return "true/false values"; } return "numbers"; } return "integers"; } private static bool TryParseBool(string text, out object? result) { if (TrueWords.Contains<string>(text, StringComparer.OrdinalIgnoreCase)) { result = true; return true; } result = false; return FalseWords.Contains<string>(text, StringComparer.OrdinalIgnoreCase); } private static bool TryParseEnum(string text, Type type, out object? result) { if (!type.IsEnum) { throw new NotSupportedException("YamlNode cannot read values of type " + type.Name); } string text2 = Enum.GetNames(type).FirstOrDefault((string n) => string.Equals(n, text, StringComparison.OrdinalIgnoreCase)); result = ((text2 == null) ? null : Enum.Parse(type, text2)); return text2 != null; } } } namespace ConfigReload { internal static class ConfigReloader { private sealed class Poller : IDisposable { private const int IntervalMilliseconds = 5000; private readonly ConfigFile config; private readonly ManualLogSource? log; private readonly string fileName; private readonly Timer timer; private DateTime lastWriteTime; private long lastLength; private string lastContent; private int reloading; public Poller(ConfigFile config, ManualLogSource? log) { this.config = config; this.log = log; fileName = Path.GetFileName(config.ConfigFilePath); Snapshot(out lastWriteTime, out lastLength); lastContent = ReadOrNull(config.ConfigFilePath) ?? ""; timer = new Timer(delegate { Poll(); }, null, 5000, 5000); } private void Poll() { try { if (SnapshotChanged()) { string text = ReadOrNull(config.ConfigFilePath); if (text != null && !(text == lastContent)) { lastContent = text; ScheduleCheck(); } } } catch (Exception ex) { ManualLogSource? obj = log; if (obj != null) { obj.LogError((object)("Watching " + fileName + " failed: " + ex.Message)); } } } private bool SnapshotChanged() { Snapshot(out var writeTime, out var length); if (writeTime == lastWriteTime && length == lastLength) { return false; } lastWriteTime = writeTime; lastLength = length; return true; } private void ScheduleCheck() { if (Interlocked.CompareExchange(ref reloading, 1, 0) == 0) { ThreadingHelper.SynchronizingObject.BeginInvoke(new Action(CheckAndReload), null); } } private void CheckAndReload() { try { if (!LoadedValues.Matches(config, lastContent)) { ManualLogSource? obj = log; if (obj != null) { obj.LogInfo((object)(fileName + " changed on disk, reloading")); } ReloadQuietly(); } } catch (Exception ex) { ManualLogSource? obj2 = log; if (obj2 != null) { obj2.LogError((object)("Failed to reload " + fileName + ", please check it for typos and formatting: " + ex.Message)); } } finally { Resnapshot(); Interlocked.Exchange(ref reloading, 0); } } private void ReloadQuietly() { bool saveOnConfigSet = config.SaveOnConfigSet; config.SaveOnConfigSet = false; try { config.Reload(); } finally { config.SaveOnConfigSet = saveOnConfigSet; } if (saveOnConfigSet) { config.Save(); } } private void Resnapshot() { Snapshot(out lastWriteTime, out lastLength); lastContent = ReadOrNull(config.ConfigFilePath) ?? lastContent; } private void Snapshot(out DateTime writeTime, out long length) { FileInfo fileInfo = new FileInfo(config.ConfigFilePath); if (fileInfo.Exists) { writeTime = fileInfo.LastWriteTimeUtc; length = fileInfo.Length; } else { writeTime = DateTime.MinValue; length = -1L; } } private static string? ReadOrNull(string path) { try { return File.Exists(path) ? File.ReadAllText(path) : null; } catch (IOException) { return null; } } public void Dispose() { timer.Dispose(); } } private static readonly HashSet<ConfigFile> held = new HashSet<ConfigFile>(); public static void HoldSaves(ConfigFile config) { if (config.SaveOnConfigSet && held.Add(config)) { config.SaveOnConfigSet = false; } } public static IDisposable Setup(ConfigFile config, ManualLogSource? log = null, bool saveNow = true) { if (held.Remove(config)) { config.SaveOnConfigSet = true; } if (saveNow) { config.Save(); } return new Poller(config, log); } } internal static class LoadedValues { private static readonly PropertyInfo? orphans = typeof(ConfigFile).GetProperty("OrphanedEntries", BindingFlags.Instance | BindingFlags.NonPublic); public static bool Matches(ConfigFile config, string content) { Dictionary<ConfigDefinition, string> dictionary = Parse(content); Dictionary<ConfigDefinition, string> dictionary2 = Loaded(config); if (dictionary2 == null) { return false; } if (dictionary.Count != dictionary2.Count) { return false; } foreach (KeyValuePair<ConfigDefinition, string> item in dictionary) { if (!dictionary2.TryGetValue(item.Key, out var value) || !string.Equals(value, item.Value, StringComparison.Ordinal)) { return false; } } return true; } private static Dictionary<ConfigDefinition, string>? Loaded(ConfigFile config) { if (!(orphans?.GetValue(config) is Dictionary<ConfigDefinition, string> dictionary)) { return null; } Dictionary<ConfigDefinition, string> dictionary2 = new Dictionary<ConfigDefinition, string>(dictionary); foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in config) { dictionary2[item.Key] = item.Value.GetSerializedValue(); } return dictionary2; } private static Dictionary<ConfigDefinition, string> Parse(string content) { //IL_009a: Unknown result type (might be due to invalid IL or missing references) //IL_00b4: Expected O, but got Unknown Dictionary<ConfigDefinition, string> dictionary = new Dictionary<ConfigDefinition, string>(); string text = ""; string[] array = content.Split(new char[1] { '\n' }); for (int i = 0; i < array.Length; i++) { string text2 = array[i].Trim(); if (text2.Length == 0 || text2.StartsWith("#", StringComparison.Ordinal)) { continue; } if (text2.StartsWith("[", StringComparison.Ordinal) && text2.EndsWith("]", StringComparison.Ordinal)) { text = text2.Substring(1, text2.Length - 2); continue; } int num = text2.IndexOf('='); if (num > 0) { dictionary[new ConfigDefinition(text, text2.Substring(0, num).Trim())] = text2.Substring(num + 1).Trim(); } } return dictionary; } } } namespace Charter { internal sealed class Amendments { private readonly Charter charter; private readonly Ledger ledger; private readonly Journal journal; private readonly string rpcName; public Amendments(Charter charter, Ledger ledger, Journal journal) { this.charter = charter; this.ledger = ledger; this.journal = journal; rpcName = "Charter_" + charter.Guid + "_Amend"; } public void Send(IClause clause) { //IL_0024: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown ZRpc rpc = charter.Receiver.Rpc; if (rpc != null && rpc.IsConnected()) { string currentToml = clause.CurrentToml; ZPackage val = new ZPackage(); val.Write(1); val.Write(charter.Guid); val.Write(clause.Section); val.Write(clause.Key); val.Write(currentToml); rpc.Invoke(rpcName, new object[1] { val }); journal.Info("amendment sent: " + clause.Section + "." + clause.Key + " = " + currentToml); } } public void OnAmend(ZRpc rpc, ZPackage pkg) { try { Receive(rpc, pkg); } catch (Exception arg) { journal.Error($"handling an amendment failed: {arg}"); } } private void Receive(ZRpc rpc, ZPackage pkg) { if (pkg.ReadInt() != 1 || pkg.ReadString() != charter.Guid) { journal.Warning("amendment with a foreign protocol or guid dropped"); return; } string section = pkg.ReadString(); string key = pkg.ReadString(); string toml = pkg.ReadString(); ZNetPeer val = Side.PeerOf(rpc); if (val != null && Side.IsServer) { string text = Accept(val, section, key, toml); if (text != null) { Reject(val, section, key, text); } } } private string? Accept(ZNetPeer peer, string section, string key, string toml) { if (!Stewardship.IsSteward(peer)) { return "you are not a steward"; } IClause clause = ledger.Find(section, key); if (clause == null) { return "unknown clause"; } if (clause.Local) { return "the clause is local"; } try { clause.Entry.SetSerializedValue(toml); } catch (Exception ex) { return "bad value: " + ex.Message; } clause.Entry.ConfigFile.Save(); journal.Info("amendment by " + Side.NameOf(peer) + " accepted: " + section + "." + key + " = " + toml); return null; } private void Reject(ZNetPeer peer, string section, string key, string reason) { journal.Warning("amendment by " + Side.NameOf(peer) + " rejected: " + section + "." + key + ": " + reason); charter.Publisher.SendNotice(peer, ledger.Find(section, key), "amendment of " + section + "." + key + " rejected: " + reason); } } internal sealed class Article<T> : IArticle { private readonly Charter charter; private readonly ArticleKind kind; private T value; public string Name { get; } public bool Standing { get; } public T Value => value; ArticleKind IArticle.Kind => kind; object IArticle.Boxed => value; public event Action? Changed; public Article(Charter charter, string name, T initial, bool standing = false) { kind = ArticleValues.KindOf(typeof(T)); this.charter = charter; Name = name; Standing = standing; value = initial; charter.Ledger.Add(this); } public void Assign(T value) { if (!charter.IsAuthor) { charter.Journal.Warning("article '" + Name + "' cannot be assigned while the server's charter binds"); } else if (!ArticleValues.Same(this.value, value)) { this.value = value; Raise(); charter.Publisher.MarkDirty(this); } } bool IArticle.Accept(object incoming) { if (ArticleValues.Same(value, incoming)) { return false; } value = (T)incoming; return true; } void IArticle.Raise() { Raise(); } private void Raise() { try { this.Changed?.Invoke(); } catch (Exception arg) { charter.Journal.Error($"a Changed handler of article '{Name}' threw: {arg}"); } } } internal interface IArticle { string Name { get; } bool Standing { get; } ArticleKind Kind { get; } object Boxed { get; } bool Accept(object incoming); void Raise(); } internal enum ArticleKind : byte { Text = 1, TextList, Integer, Number, Toggle } internal static class ArticleValues { public static ArticleKind KindOf(Type type) { if (type == typeof(string)) { return ArticleKind.Text; } if (type == typeof(List<string>)) { return ArticleKind.TextList; } if (type == typeof(int)) { return ArticleKind.Integer; } if (type == typeof(float)) { return ArticleKind.Number; } if (type == typeof(bool)) { return ArticleKind.Toggle; } throw new ArgumentException("Charter articles hold string, List<string>, int, float or bool, not " + type.Name); } public static void Write(ZPackage pkg, ArticleKind kind, object? value) { switch (kind) { case ArticleKind.Text: pkg.Write((value as string) ?? ""); break; case ArticleKind.TextList: WriteList(pkg, value as List<string>); break; case ArticleKind.Integer: pkg.Write((value is int num2) ? num2 : 0); break; case ArticleKind.Number: pkg.Write((value is float num3) ? num3 : 0f); break; case ArticleKind.Toggle: { bool flag = default(bool); int num; if (value is bool) { flag = (bool)value; num = 1; } else { num = 0; } pkg.Write((byte)((uint)num & (flag ? 1u : 0u)) != 0); break; } default: throw new ArgumentException($"unknown article kind {kind}"); } } public static object Read(ZPackage pkg, ArticleKind kind) { return kind switch { ArticleKind.Text => pkg.ReadString(), ArticleKind.TextList => ReadList(pkg), ArticleKind.Integer => pkg.ReadInt(), ArticleKind.Number => pkg.ReadSingle(), ArticleKind.Toggle => pkg.ReadBool(), _ => throw new ArgumentException($"unknown article kind {kind}"), }; } public static int Size(ArticleKind kind, object? value) { return kind switch { ArticleKind.Text => (value as string)?.Length ?? 0, ArticleKind.TextList => (value as List<string>)?.Sum((string s) => s?.Length ?? 0) ?? 0, _ => 4, }; } public static bool Same(object? a, object? b) { if (a is List<string> first) { List<string> second = (b as List<string>) ?? new List<string>(); return first.SequenceEqual<string>(second, StringComparer.Ordinal); } if (b is List<string> list) { if (list.Count == 0) { return a == null; } return false; } return object.Equals(a, b); } private static void WriteList(ZPackage pkg, List<string>? list) { pkg.Write(list?.Count ?? 0); foreach (string item in list ?? new List<string>()) { pkg.Write(item ?? ""); } } private static List<string> ReadList(ZPackage pkg) { int num = pkg.ReadInt(); List<string> list = new List<string>(Math.Max(0, num)); for (int i = 0; i < num; i++) { list.Add(pkg.ReadString()); } return list; } } internal sealed class ChangeRouter { private readonly Charter charter; private readonly Ledger ledger; private readonly Journal journal; private readonly HashSet<ConfigFile> watched = new HashSet<ConfigFile>(); public ChangeRouter(Charter charter, Ledger ledger, Journal journal) { this.charter = charter; this.ledger = ledger; this.journal = journal; } public void Watch(ConfigFile file) { if (watched.Add(file)) { file.SettingChanged += OnSettingChanged; } } private void OnSettingChanged(object sender, SettingChangedEventArgs args) { try { if (!ledger.Applying) { Route(args.ChangedSetting); } } catch (Exception arg) { journal.Error($"handling a change of {args.ChangedSetting.Definition} failed: {arg}"); } } private void Route(ConfigEntryBase entry) { IClause clause = ledger.Find(entry); if (clause == null) { return; } clause.RememberOwn(); if (Side.IsServer) { charter.Publisher.MarkDirty(clause); return; } Receiver receiver = charter.Receiver; if (receiver.Bound && !clause.Local) { if (receiver.Steward) { charter.Amendments.Send(clause); return; } journal.Info(clause.Section + "." + clause.Key + " is bound by the server, reverting to " + clause.AuthorToml); clause.ApplyAuthor(); } } } public sealed class Charter { private bool lastBound; private bool lastSteward; public string Guid { get; } public string Title { get; } public string Version { get; } public string OldestAccepted { get; } public bool Mandatory { get; } public bool IsBound { get { if (!Side.IsServer) { return Receiver.Bound; } return Ledger.BindingOn; } } public bool IsAuthor { get { if (!Side.IsServer) { return !Receiver.Bound; } return true; } } public bool MayAmend { get { if (IsBound) { return IsSteward; } return true; } } public bool IsSteward { get { if (!Side.IsServer) { return Receiver.Steward; } return true; } } public DateTime? LastPush { get { if (!Side.IsServer) { return Receiver.LastPush; } return null; } } public int PushedBytes => Receiver.PushedBytes; public static Verbosity Verbosity { get; set; } = Verbosity.Normal; internal Journal Journal { get; } internal Ledger Ledger { get; } internal Receiver Receiver { get; } internal Courier Courier { get; } internal Publisher Publisher { get; } internal Stewardship Stewardship { get; } internal Amendments Amendments { get; } internal ChangeRouter Router { get; } internal ReadOnlyTags Tags { get; } public event Action<bool>? Pushed; public event Action? StewardChanged; public event Action? BindingChanged; public Charter(string guid, string title, string version, string? oldestAccepted = null, bool mandatory = true) { Guid = guid; Title = title; Version = version; OldestAccepted = oldestAccepted ?? version; Mandatory = mandatory; Journal = new Journal(title); Ledger = new Ledger(); Receiver = new Receiver(this, Ledger, Journal); Courier = new Courier(guid, Journal); Publisher = new Publisher(Ledger, Courier, Journal); Stewardship = new Stewardship(Courier, Publisher); Amendments = new Amendments(this, Ledger, Journal); Router = new ChangeRouter(this, Ledger, Journal); Tags = new ReadOnlyTags(); lastSteward = IsSteward; Enrol(); } public Clause<T> Clause<T>(ConfigEntry<T> entry, bool local = false) { Clause<T> clause = new Clause<T>(entry, local, Ledger); Ledger.Add(clause); Router.Watch(((ConfigEntryBase)entry).ConfigFile); return clause; } public void Binding(ConfigEntry<bool> entry) { Clause<bool> clause = Clause<bool>(entry); Ledger.SetBinding(clause, () => entry.Value); } public static void Install(Harmony harmony) { GameHooks.Install(harmony); } internal void Attach(ZNetPeer peer, bool server) { if (server) { peer.m_rpc.Register<ZPackage>("Charter_" + Guid + "_Amend", (Action<ZRpc, ZPackage>)Amendments.OnAmend); } else { peer.m_rpc.Register<ZPackage>("Charter_" + Guid + "_Push", (Action<ZRpc, ZPackage>)Receiver.OnPush); } } internal void Tick(float now) { Publisher.Flush(); Courier.Tick(); Stewardship.Tick(now); Receiver.Poll(); NoteChanges(); } internal void AfterPush(bool first) { NoteChanges(); try { this.Pushed?.Invoke(first); } catch (Exception arg) { Journal.Error($"a Pushed handler threw: {arg}"); } } internal void AfterSessionEnd() { NoteChanges(); } private void Enrol() { GameHooks.Track(this); Family.Register(new FamilyEntry(Guid, Title, Version, OldestAccepted, Mandatory)); Family.SetStatus(Guid, () => Reports.Status(this)); Family.SetDiff(Guid, () => Reports.Diff(this)); Family.SetVerbosity(Guid, delegate(int level) { Verbosity = (Verbosity)level; }); } private void NoteChanges() { bool isBound = IsBound; bool isSteward = IsSteward; Tags.Update(isBound && !isSteward, Ledger); if (isBound != lastBound) { lastBound = isBound; Raise(this.BindingChanged, "BindingChanged"); } if (isSteward != lastSteward) { lastSteward = isSteward; Raise(this.StewardChanged, "StewardChanged"); } } private void Raise(Action? handler, string name) { try { handler?.Invoke(); } catch (Exception arg) { Journal.Error($"a {name} handler threw: {arg}"); } } } public sealed class Clause<T> : IClause { private readonly Ledger ledger; private T own; private T author; public ConfigEntry<T> Entry { get; } public bool Local { get; } public T AuthorValue { get { if (!Side.IsServer) { return author; } return Entry.Value; } } public T OwnValue => own; ConfigEntryBase IClause.Entry => (ConfigEntryBase)(object)Entry; string IClause.Section => ((ConfigEntryBase)Entry).Definition.Section; string IClause.Key => ((ConfigEntryBase)Entry).Definition.Key; public string AuthorToml { get; private set; } string IClause.OwnToml => TomlTypeConverter.ConvertToString((object)own, typeof(T)); string IClause.CurrentToml => ((ConfigEntryBase)Entry).GetSerializedValue(); internal Clause(ConfigEntry<T> entry, bool local, Ledger ledger) { Entry = entry; Local = local; this.ledger = ledger; own = entry.Value; author = default(T); AuthorToml = ""; } void IClause.RememberOwn() { own = Entry.Value; } bool IClause.TakeAuthor(string toml) { try { author = TomlTypeConverter.ConvertToValue<T>(toml); AuthorToml = toml; return true; } catch (Exception) { return false; } } void IClause.ApplyAuthor() { ledger.Quietly((ConfigEntryBase)(object)Entry, delegate { Entry.Value = author; }); } void IClause.RestoreOwn() { ledger.Quietly((ConfigEntryBase)(object)Entry, delegate { Entry.Value = own; }); } } internal interface IClause { ConfigEntryBase Entry { get; } string Section { get; } string Key { get; } bool Local { get; } string AuthorToml { get; } string OwnToml { get; } string CurrentToml { get; } void RememberOwn(); bool TakeAuthor(string toml); void ApplyAuthor(); void RestoreOwn(); } internal static class ConsoleCommands { [CompilerGenerated] private static class <>O { public static ConsoleEvent <0>__Run; public static Func<FamilyEntry, string> <1>__Status; public static Func<FamilyEntry, string> <2>__Diff; } private const string Usage = "charter status | charter diff [title] | charter versions | charter trace on|off"; private static bool registered; public static void Register() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002d: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown if (!registered) { registered = true; object obj = <>O.<0>__Run; if (obj == null) { ConsoleEvent val = Run; <>O.<0>__Run = val; obj = (object)val; } new ConsoleCommand("charter", "Charter: charter status | charter diff [title] | charter versions | charter trace on|off", (ConsoleEvent)obj, false, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false); } } private static void Run(ConsoleEventArgs args) { string text = ((args.Length > 1) ? args[1].ToLowerInvariant() : "status"); string rest = ((args.Length > 2) ? string.Join(" ", args.Args.Skip(2)) : ""); foreach (string item in (text switch { "status" => Family.Entries().Select(Status), "diff" => (from e in Family.Entries() where Matches(e, rest) select e).Select(Diff), "versions" => Versions(), "trace" => Trace(rest), _ => new string[1] { "charter status | charter diff [title] | charter versions | charter trace on|off" }, }).SelectMany((string l) => l.Split(new char[1] { '\n' }))) { args.Context.AddString(item); GameHooks.LeadJournal?.Info(item); } } private static bool Matches(FamilyEntry entry, string title) { if (title.Length != 0) { return entry.Title.IndexOf(title, StringComparison.OrdinalIgnoreCase) >= 0; } return true; } private static string Status(FamilyEntry entry) { return Family.Status(entry.Guid)?.Invoke() ?? (entry.Title + " " + entry.Version + ": no status"); } private static string Diff(FamilyEntry entry) { return Family.Diff(entry.Guid)?.Invoke() ?? (entry.Title + ": no diff"); } private static IEnumerable<string> Versions() { yield return "This side:"; foreach (FamilyEntry item in Family.Entries()) { yield return " " + item.Describe(); } yield return (FamilyRpc.ServerRegistry == null) ? "Server: not received (not connected, or the server runs no Charter mod)" : "Server, as received at join:"; foreach (FamilyEntry item2 in FamilyRpc.ServerRegistry ?? new List<FamilyEntry>()) { yield return " " + item2.Describe(); } } private static IEnumerable<string> Trace(string mode) { if (mode != "on" && mode != "off") { return new string[1] { "charter status | charter diff [title] | charter versions | charter trace on|off" }; } int num = ((!(mode == "on")) ? 1 : 2); foreach (FamilyEntry item in Family.Entries()) { Family.Verbosity(item.Guid)?.Invoke(num); } return new string[1] { $"Charter verbosity is now {(Verbosity)num} for every mod" }; } } internal sealed class Courier { private sealed class Lane { public int Sequence; public bool Steward; public readonly Queue<ZPackage> Fragments = new Queue<ZPackage>(); } private const int HoldAboveQueued = 524288; private const float PruneSeconds = 5f; private readonly Dictionary<ZNetPeer, Lane> lanes = new Dictionary<ZNetPeer, Lane>(); private readonly List<ZNetPeer> peers = new List<ZNetPeer>(); private int queued; private float nextPrune; private readonly string guid; private readonly string rpcName; private readonly Journal journal; public Courier(string guid, Journal journal) { this.guid = guid; this.journal = journal; rpcName = "Charter_" + guid + "_Push"; } public bool? LastSteward(ZNetPeer peer) { if (!lanes.TryGetValue(peer, out Lane value)) { return null; } return value.Steward; } public void Send(ZNetPeer peer, PushBody body, string reason) { Send(peer, new Parcel(body), reason); } public void Send(ZNetPeer peer, Parcel parcel, string reason) { if (parcel.TooLarge) { journal.Error($"push refused: {parcel.BodyBytes} bytes exceed {8388608} ({guid})"); return; } if (!lanes.TryGetValue(peer, out Lane value)) { value = (lanes[peer] = new Lane()); } value.Steward = parcel.Steward; int num = ++value.Sequence; List<ZPackage> list = Fragmenter.Split(guid, num, parcel); foreach (ZPackage item in list) { value.Fragments.Enqueue(item); } queued += list.Count; journal.Info($"push #{num} to {Side.NameOf(peer)} ({reason}): {parcel.Clauses} clause(s), {parcel.Articles} article(s), " + string.Format("{0} bytes{1}, {2} fragment(s)", parcel.WireBytes, parcel.Compressed ? " compressed" : "", list.Count)); } public void Tick() { float unscaledTime = Time.unscaledTime; if (lanes.Count == 0 || (queued == 0 && unscaledTime < nextPrune)) { return; } nextPrune = unscaledTime + 5f; peers.Clear(); foreach (ZNetPeer key in lanes.Keys) { peers.Add(key); } foreach (ZNetPeer peer in peers) { SendNext(peer); } } private void SendNext(ZNetPeer peer) { if (!Side.IsPresent(peer)) { queued -= lanes[peer].Fragments.Count; lanes.Remove(peer); return; } Lane lane = lanes[peer]; if (lane.Fragments.Count != 0 && peer.m_socket.GetSendQueueSize() <= 524288) { peer.m_rpc.Invoke(rpcName, new object[1] { lane.Fragments.Dequeue() }); queued--; if (journal.Tracing) { journal.Trace($"fragment sent to {Side.NameOf(peer)}, {lane.Fragments.Count} left in the queue"); } } } } internal static class Family { private const string RegistryKey = "Charter.Family"; private const string LeadKey = "Charter.Family.Lead"; public static void Register(FamilyEntry entry) { Registry()[entry.Guid] = entry.Format(); } public static bool ClaimLead(string guid) { if (AppDomain.CurrentDomain.GetData("Charter.Family.Lead") is string text) { return text == guid; } AppDomain.CurrentDomain.SetData("Charter.Family.Lead", guid); return true; } public static List<FamilyEntry> Entries() { return (from pair in Registry() select FamilyEntry.Parse(pair.Key, pair.Value)).OrderBy<FamilyEntry, string>((FamilyEntry e) => e.Title, StringComparer.OrdinalIgnoreCase).ToList(); } public static void SetStatus(string guid, Func<string> report) { AppDomain.CurrentDomain.SetData("Charter.Status." + guid, report); } public static void SetDiff(string guid, Func<string> report) { AppDomain.CurrentDomain.SetData("Charter.Diff." + guid, report); } public static void SetVerbosity(string guid, Action<int> set) { AppDomain.CurrentDomain.SetData("Charter.Verbosity." + guid, set); } public static Func<string>? Status(string guid) { return AppDomain.CurrentDomain.GetData("Charter.Status." + guid) as Func<string>; } public static Func<string>? Diff(string guid) { return AppDomain.CurrentDomain.GetData("Charter.Diff." + guid) as Func<string>; } public static Action<int>? Verbosity(string guid) { return AppDomain.CurrentDomain.GetData("Charter.Verbosity." + guid) as Action<int>; } private static Dictionary<string, string> Registry() { if (AppDomain.CurrentDomain.GetData("Charter.Family") is Dictionary<string, string> result) { return result; } Dictionary<string, string> dictionary = new Dictionary<string, string>(StringComparer.Ordinal); AppDomain.CurrentDomain.SetData("Charter.Family", dictionary); return dictionary; } } internal static class FamilyCheck { private const string Alphabet = "abcdefghijklmnopqrstuvwxyz0123456789"; private static readonly Random Dice = new Random(); public static List<string> Compare(List<FamilyEntry> server, List<FamilyEntry> client) { List<string> list = new List<string>(); foreach (FamilyEntry mine in server) { FamilyEntry familyEntry = client.FirstOrDefault((FamilyEntry c) => c.Guid == mine.Guid); if (familyEntry == null) { if (mine.Mandatory) { list.Add(Outdated(mine, "no copy")); } } else if (Older(familyEntry.Version, mine.OldestAccepted) || Older(mine.Version, familyEntry.OldestAccepted)) { list.Add(Outdated(mine, familyEntry.Version)); } } list.AddRange(client.Where((FamilyEntry c) => c.Mandatory && server.All((FamilyEntry s) => s.Guid !=