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 YouAreNotWorthy v1.0.0
YouAreNotWorthy.dll
Decompiled 10 hours ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.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.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using Steamworks; using TMPro; using UnityEngine; using YamlDotNet.Core; using YamlDotNet.Core.Events; using YamlDotNet.Core.ObjectPool; using YamlDotNet.Core.Tokens; using YamlDotNet.Helpers; using YamlDotNet.RepresentationModel; 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: AssemblyTitle("YouAreNotWorthy")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("YouAreNotWorthy")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")] [assembly: AssemblyFileVersion("1.0.0")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)] internal sealed class NullableAttribute : Attribute { public readonly byte[] NullableFlags; public NullableAttribute(byte P_0) { NullableFlags = new byte[1] { P_0 }; } public NullableAttribute(byte[] P_0) { NullableFlags = P_0; } } [CompilerGenerated] [Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } } namespace YouAreNotWorthy { public enum KeyQueryResult : byte { Invalid, Unavailable, PersonalMissing, PersonalPresent, SharedMissing, SharedPresent } public static class YouAreNotWorthyApi { public const int ApiVersion = 1; public static KeyQueryResult QueryLocal(string? key) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Unknown result type (might be due to invalid IL or missing references) try { if (!TryPrepareKey(key, out string preparedKey, out bool isPersonal, out KeyQueryResult failure)) { return failure; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && ((Character)localPlayer).IsOwner()) { ZDOID zDOID = ((Character)localPlayer).GetZDOID(); if (!((ZDOID)(ref zDOID)).IsNone()) { return isPersonal ? ToPersonalResult(PlayerKeys.HasNativeKey(localPlayer, preparedKey)) : QueryShared(preparedKey); } } return KeyQueryResult.Unavailable; } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogError((object)$"YNW API failed to evaluate local key '{key ?? string.Empty}': {arg}"); return KeyQueryResult.Unavailable; } } public static bool TryShowLocalMissingRequirement(string? key) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) try { if (!TryPrepareKey(key, out string preparedKey, out bool _, out KeyQueryResult _)) { return false; } Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && ((Character)localPlayer).IsOwner()) { ZDOID zDOID = ((Character)localPlayer).GetZDOID(); if (!((ZDOID)(ref zDOID)).IsNone()) { return ItemRestriction.ShowBlockedMessage(RequirementTextResolver.Resolve(preparedKey)); } } return false; } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogError((object)$"YNW API failed to show local missing requirement '{key ?? string.Empty}': {arg}"); return false; } } public static KeyQueryResult QueryPeer(ZNetPeer? peer, string? key) { try { if (!TryPrepareKey(key, out string preparedKey, out bool isPersonal, out KeyQueryResult failure)) { return failure; } if (!TryGetAuthenticatedPeerCharacter(peer, out ZDO character)) { return KeyQueryResult.Unavailable; } if (!isPersonal) { return QueryShared(preparedKey); } bool hasKey; return (!PersonalKeySnapshot.TryHas(character, preparedKey, out hasKey)) ? KeyQueryResult.Unavailable : ToPersonalResult(hasKey); } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogError((object)$"YNW API failed to evaluate peer key '{key ?? string.Empty}': {arg}"); return KeyQueryResult.Unavailable; } } private static bool TryPrepareKey(string? key, out string preparedKey, out bool isPersonal, out KeyQueryResult failure) { preparedKey = (key ?? string.Empty).Trim(); isPersonal = false; failure = KeyQueryResult.Invalid; if (IsInvalidKey(preparedKey)) { return false; } if (!YouAreNotWorthyPlugin.IsApiReady) { failure = KeyQueryResult.Unavailable; return false; } if (ProgressionIndex.TryRegisterPersonalKey(preparedKey, out string canonicalKey)) { preparedKey = canonicalKey; isPersonal = true; return true; } return ProgressionIndex.IsSharedWorldKey(preparedKey); } private static bool IsInvalidKey(string key) { //IL_005f: Unknown result type (might be due to invalid IL or missing references) //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0076: Invalid comparison between Unknown and I4 //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Invalid comparison between Unknown and I4 if (key.Length == 0) { return true; } string text = key; for (int i = 0; i < text.Length; i++) { if (char.IsControl(text[i])) { return true; } } GlobalKeys val = default(GlobalKeys); string keyValue = ZoneSystem.GetKeyValue(key, ref text, ref val); if (long.TryParse(keyValue, NumberStyles.Integer, CultureInfo.InvariantCulture, out var _)) { return true; } GlobalKeys result2; bool flag = Enum.TryParse<GlobalKeys>(keyValue, ignoreCase: true, out result2) && Enum.IsDefined(typeof(GlobalKeys), result2); if (flag) { bool flag2 = (((int)result2 == 32 || (int)result2 == 43) ? true : false); flag = flag2; } return flag; } private static KeyQueryResult QueryShared(string key) { if (!((Object)(object)ZoneSystem.instance == (Object)null)) { if (!ProgressionIndex.HasWorldKey(key)) { return KeyQueryResult.SharedMissing; } return KeyQueryResult.SharedPresent; } return KeyQueryResult.Unavailable; } private static KeyQueryResult ToPersonalResult(bool hasKey) { if (!hasKey) { return KeyQueryResult.PersonalMissing; } return KeyQueryResult.PersonalPresent; } private static bool TryGetAuthenticatedPeerCharacter(ZNetPeer? peer, out ZDO character) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_00d0: Unknown result type (might be due to invalid IL or missing references) //IL_00d6: Unknown result type (might be due to invalid IL or missing references) character = null; ZNet instance = ZNet.instance; ZDOMan instance2 = ZDOMan.instance; if (peer == null || (Object)(object)instance == (Object)null || !instance.IsServer() || instance2 == null || !peer.IsReady() || peer.m_rpc == null || !peer.m_rpc.IsConnected() || ((ZDOID)(ref peer.m_characterID)).IsNone() || ((ZDOID)(ref peer.m_characterID)).UserID != peer.m_uid || instance.GetPeer(peer.m_rpc) != peer) { return false; } ZDO zDO = instance2.GetZDO(peer.m_characterID); if (zDO == null || !zDO.IsValid() || zDO.GetOwner() != peer.m_uid) { return false; } Player val = null; foreach (Player allPlayer in Player.GetAllPlayers()) { if (!((Object)(object)allPlayer == (Object)null) && ((Character)allPlayer).GetOwner() == peer.m_uid && !(((Character)allPlayer).GetZDOID() != peer.m_characterID)) { if ((Object)(object)val != (Object)null) { return false; } val = allPlayer; } } long num = zDO.GetLong(ZDOVars.s_playerID, 0L); ZNetView val2 = (((Object)(object)val != (Object)null) ? ((Component)val).GetComponent<ZNetView>() : null); if (num == 0L || (Object)(object)val == (Object)null || val.GetPlayerID() != num || (Object)(object)val2 == (Object)null || !val2.IsValid() || val2.GetZDO() != zDO) { return false; } character = zDO; return true; } } internal sealed class ProgressionConfig { internal List<DefeatKeyRule> DefeatKeys { get; } = new List<DefeatKeyRule>(); internal List<ItemTierConfig> Tiers { get; } = new List<ItemTierConfig>(); } internal sealed class DefeatKeyRule { internal List<string> Prefabs { get; } = new List<string>(); internal string Key { get; set; } = ""; } internal sealed class ItemTierConfig { internal string Id { get; } internal string RequiredKey { get; } internal List<string> Resources { get; } internal ItemTierConfig(string id, string requiredKey, List<string> resources) { Id = id; RequiredKey = requiredKey; Resources = resources; } } internal static class ProgressionConfigLoader { internal const string ConfigFileName = "progression.yml"; internal const string SyncedYamlIdentifier = "ProgressionYaml"; internal static string ConfigDirectory => Path.Combine(Paths.ConfigPath, "YouAreNotWorthy"); internal static string ConfigPath => Path.Combine(ConfigDirectory, "progression.yml"); private static bool IsConfigured { get; set; } internal static string AppliedYaml { get; private set; } = ""; private static string DefaultProgressionYaml => LoadEmbeddedYaml(".progression.default.yml"); internal static bool LoadOrCreate() { if (TryReadLocal(out string yaml, createIfMissing: true) && TryApply(yaml, ConfigPath)) { return true; } PreserveLastKnownGoodOrUseDefault(); return IsConfigured; } internal static bool TryReadValidLocalYaml(out string yaml) { yaml = ""; ProgressionConfig progression; if (TryReadLocal(out yaml)) { return TryParseAndValidate(yaml, ConfigPath, out progression); } return false; } internal static bool ApplyLocalYaml(string yaml) { return TryApply(yaml, ConfigPath); } internal static bool ApplySyncedYaml(string yaml) { return TryApply(yaml, "ServerSync progression.yml"); } private static bool TryReadLocal(out string yaml, bool createIfMissing = false) { yaml = ""; try { Directory.CreateDirectory(ConfigDirectory); if (!File.Exists(ConfigPath)) { if (!createIfMissing) { YouAreNotWorthyPlugin.Log.LogWarning((object)("Configuration file not found: " + ConfigPath)); return false; } File.WriteAllText(ConfigPath, DefaultProgressionYaml); } yaml = File.ReadAllText(ConfigPath); return true; } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogError((object)$"Failed to read YNW YAML configuration: {arg}"); return false; } } private static bool TryApply(string yaml, string source) { if (!TryParseAndValidate(yaml, source, out ProgressionConfig progression)) { return false; } try { ProgressionIndex.Configure(progression); RestrictionEvaluator.InvalidateItemCache(); RequirementTextResolver.InvalidateSources(); KeyReferenceWriter.MarkItemReferenceDirty(); AppliedYaml = yaml; IsConfigured = true; return true; } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogError((object)$"Failed to apply {source}: {arg}"); return false; } } private static bool TryParseAndValidate(string yaml, string source, out ProgressionConfig progression) { progression = new ProgressionConfig(); if (string.IsNullOrWhiteSpace(yaml)) { YouAreNotWorthyPlugin.Log.LogError((object)("Invalid " + source + ": The YAML document is empty.")); return false; } List<string> list = new List<string>(); try { YamlStream yamlStream = new YamlStream(); using StringReader input = new StringReader(yaml); yamlStream.Load(input); if (yamlStream.Documents.Count != 1 || !(yamlStream.Documents[0].RootNode is YamlMappingNode root)) { YouAreNotWorthyPlugin.Log.LogError((object)("Invalid " + source + ": Expected one progression mapping document.")); return false; } ParseRoot(root, progression, list); } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogError((object)$"Failed to parse {source}: {arg}"); return false; } if (list.Count == 0) { Validate(progression, list); } if (list.Count == 0) { return true; } foreach (string item in list) { YouAreNotWorthyPlugin.Log.LogError((object)("Invalid " + source + ": " + item)); } return false; } private static void ParseRoot(YamlMappingNode root, ProgressionConfig progression, List<string> errors) { bool flag = false; HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase); foreach (KeyValuePair<YamlNode, YamlNode> child in root.Children) { if (!TryReadNonEmptyScalar(child.Key, out string value)) { errors.Add("Every root entry must have a non-empty scalar name."); } else if (string.Equals(value, "defeatKeys", StringComparison.OrdinalIgnoreCase)) { if (!string.Equals(value, "defeatKeys", StringComparison.Ordinal)) { errors.Add("Reserved root entry '" + value + "' must be spelled exactly 'defeatKeys'."); continue; } if (flag) { errors.Add("Duplicate root entry 'defeatKeys'."); continue; } flag = true; ParseDefeatKeys(child.Value, progression.DefeatKeys, errors); } else if (!hashSet.Add(value)) { errors.Add("Duplicate item tier id '" + value + "'."); } else if (child.Value is YamlSequenceNode sequence) { List<string> list = ParseResources(sequence, "tier '" + value + "'", errors); if (list.Count == 0) { errors.Add("Unrestricted tier '" + value + "' must contain at least one resource."); } progression.Tiers.Add(new ItemTierConfig(value, "", list)); } else if (child.Value is YamlMappingNode mapping) { ParseRestrictedTier(value, mapping, progression.Tiers, errors); } else { errors.Add("Tier '" + value + "' must be either a resource sequence or a mapping with requiredKey and optional resources."); } } } private static void ParseDefeatKeys(YamlNode node, List<DefeatKeyRule> destination, List<string> errors) { if (!(node is YamlSequenceNode yamlSequenceNode)) { errors.Add("defeatKeys must be a sequence."); return; } for (int i = 0; i < yamlSequenceNode.Children.Count; i++) { string text = $"defeatKeys[{i}]"; if (!(yamlSequenceNode.Children[i] is YamlMappingNode yamlMappingNode)) { errors.Add(text + " must be a mapping with prefabs and key."); continue; } DefeatKeyRule defeatKeyRule = new DefeatKeyRule(); bool flag = false; bool flag2 = false; HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (KeyValuePair<YamlNode, YamlNode> child in yamlMappingNode.Children) { if (!TryReadNonEmptyScalar(child.Key, out string value)) { errors.Add(text + " contains an empty or non-scalar field name."); continue; } if (!hashSet.Add(value)) { errors.Add(text + " contains duplicate field '" + value + "'."); continue; } if (!(value == "prefabs")) { if (value == "key") { flag2 = true; if (!TryReadNonEmptyScalar(child.Value, out string value2)) { errors.Add(text + ".key must be a non-empty scalar."); } else { defeatKeyRule.Key = value2; } } else { errors.Add(text + " contains unknown field '" + value + "'."); } continue; } flag = true; if (!(child.Value is YamlSequenceNode yamlSequenceNode2)) { errors.Add(text + ".prefabs must be a sequence."); continue; } foreach (YamlNode child2 in yamlSequenceNode2.Children) { if (!TryReadNonEmptyScalar(child2, out string value3)) { errors.Add(text + ".prefabs contains an empty or non-scalar prefab."); } else { defeatKeyRule.Prefabs.Add(value3); } } } if (!flag) { errors.Add(text + " is missing required field 'prefabs'."); } if (!flag2) { errors.Add(text + " is missing required field 'key'."); } destination.Add(defeatKeyRule); } } private static void ParseRestrictedTier(string id, YamlMappingNode mapping, List<ItemTierConfig> destination, List<string> errors) { string text = "tier '" + id + "'"; string value = ""; List<string> resources = new List<string>(); bool flag = false; HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (KeyValuePair<YamlNode, YamlNode> child in mapping.Children) { if (!TryReadNonEmptyScalar(child.Key, out string value2)) { errors.Add(text + " contains an empty or non-scalar field name."); } else if (!hashSet.Add(value2)) { errors.Add(text + " contains duplicate field '" + value2 + "'."); } else if (!(value2 == "requiredKey")) { if (value2 == "resources") { if (!(child.Value is YamlSequenceNode sequence)) { errors.Add(text + ".resources must be a sequence."); } else { resources = ParseResources(sequence, text, errors); } } else { errors.Add(text + " contains unknown field '" + value2 + "'."); } } else { flag = true; if (!TryReadNonEmptyScalar(child.Value, out value)) { errors.Add(text + ".requiredKey must be a non-empty scalar."); } } } if (!flag) { errors.Add(text + " mapping is missing required field 'requiredKey'."); } destination.Add(new ItemTierConfig(id, value, resources)); } private static List<string> ParseResources(YamlSequenceNode sequence, string label, List<string> errors) { List<string> list = new List<string>(); foreach (YamlNode child in sequence.Children) { if (!TryReadNonEmptyScalar(child, out string value)) { errors.Add(label + " contains an empty or non-scalar resource."); } else { list.Add(value); } } return list; } private static bool TryReadNonEmptyScalar(YamlNode node, out string value) { value = ((!(node is YamlScalarNode yamlScalarNode)) ? "" : (yamlScalarNode.Value?.Trim() ?? "")); return value.Length > 0; } private static void Validate(ProgressionConfig progression, List<string> errors) { ValidateDefeatKeys(progression, errors); ValidateItemTiers(progression, errors); try { ProgressionIndex.ValidateConfiguration(progression); } catch (Exception ex) { errors.Add(ex.Message); } } private static void ValidateDefeatKeys(ProgressionConfig progression, List<string> errors) { for (int i = 0; i < progression.DefeatKeys.Count; i++) { DefeatKeyRule defeatKeyRule = progression.DefeatKeys[i]; string text = $"defeatKeys[{i}]"; if (string.IsNullOrWhiteSpace(defeatKeyRule.Key)) { errors.Add(text + " has an empty key."); } if (defeatKeyRule.Prefabs.Count == 0) { errors.Add(text + " must contain at least one prefab."); continue; } HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (string prefab in defeatKeyRule.Prefabs) { string text2 = ValheimNameUtils.NormalizePrefabName(prefab); if (text2.Length == 0) { errors.Add(text + " contains an empty prefab."); } else if (!hashSet.Add(text2)) { errors.Add(text + " contains duplicate prefab '" + prefab + "'."); } } } } private static void ValidateItemTiers(ProgressionConfig progression, List<string> errors) { HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); foreach (ItemTierConfig tier in progression.Tiers) { foreach (string resource in tier.Resources) { string text = ValheimNameUtils.NormalizeResourceName(resource); if (text.Length == 0) { errors.Add("Tier '" + tier.Id + "' contains invalid resource '" + resource + "'."); } else if (!hashSet.Add(text)) { errors.Add("Resource '" + resource + "' appears more than once after normalization; every resource must belong to one tier."); } } } } private static void PreserveLastKnownGoodOrUseDefault() { if (IsConfigured) { YouAreNotWorthyPlugin.Log.LogError((object)"Keeping the last-known-good YNW YAML configuration."); return; } try { if (!TryApply(DefaultProgressionYaml, "the embedded default progression YAML")) { YouAreNotWorthyPlugin.Log.LogError((object)"Failed to initialize the embedded default YNW progression YAML."); } else { YouAreNotWorthyPlugin.Log.LogWarning((object)"Using the embedded default YNW progression YAML until the local file is fixed."); } } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogError((object)$"Failed to initialize the embedded default YNW progression YAML: {arg}"); } } private static string LoadEmbeddedYaml(string suffix) { string text = typeof(ProgressionConfigLoader).Assembly.GetManifestResourceNames().FirstOrDefault((string name) => name.EndsWith(suffix, StringComparison.Ordinal)); if (string.IsNullOrEmpty(text)) { throw new InvalidDataException("The embedded default YAML ending in '" + suffix + "' was not found."); } using Stream stream = typeof(ProgressionConfigLoader).Assembly.GetManifestResourceStream(text); if (stream == null) { throw new InvalidDataException("The embedded default YAML '" + text + "' could not be opened."); } using StreamReader streamReader = new StreamReader(stream); return streamReader.ReadToEnd(); } } internal enum ItemReferenceWriteResult { Updated, Unchanged, NotReady, ShuttingDown } internal static class ItemReferenceWriter { private sealed class OutputItem { internal string PrefabName { get; } internal string ItemType { get; } internal string RequiredKey { get; } internal int RequiredKeyRank { get; } internal string Owner { get; } internal OutputItem(GuardedItemReference item, string owner) { //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) PrefabName = item.PrefabName; ItemType = ((object)item.ItemType/*cast due to .constrained prefix*/).ToString(); RequiredKey = item.RequiredKey; RequiredKeyRank = ((item.RequiredKey.Length == 0) ? (-1) : (ProgressionIndex.TryGetItemTierKeyRank(item.RequiredKey, out var rank) ? rank : int.MaxValue)); Owner = PrefabOwnerResolver.NormalizeOwnerName(owner); } } internal const string ReferenceFileName = "items.reference.yml"; private const float ExistenceCheckSeconds = 5f; private static readonly UTF8Encoding Utf8WithoutBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); private static readonly ISerializer EntrySerializer = new SerializerBuilder().Build(); private static readonly object Sync = new object(); private static readonly Dictionary<string, int> ItemTypeSortOrder = new Dictionary<string, int>(StringComparer.Ordinal) { ["OneHandedWeapon"] = 0, ["TwoHandedWeapon"] = 1, ["TwoHandedWeaponLeft"] = 2, ["Bow"] = 3, ["Shield"] = 4, ["Torch"] = 5, ["Tool"] = 6, ["Attach_Atgeir"] = 7, ["Helmet"] = 10, ["Chest"] = 11, ["Legs"] = 12, ["Shoulder"] = 13, ["Hands"] = 14, ["Utility"] = 15, ["Trinket"] = 16, ["Consumable"] = 20, ["Fish"] = 21, ["Ammo"] = 30, ["AmmoNonEquipable"] = 31, ["Material"] = 40, ["Trophy"] = 41, ["Misc"] = 50, ["None"] = 51 }; private static bool _dirty = true; private static bool _shutdown; private static long _revision = 1L; private static float _nextExistenceCheck; internal static string ReferencePath => Path.Combine(ProgressionConfigLoader.ConfigDirectory, "items.reference.yml"); internal static void Reset() { PrefabOwnerResolver.Invalidate(); lock (Sync) { _shutdown = false; _dirty = true; _revision++; _nextExistenceCheck = 0f; } } internal static void MarkDirty(bool ownerSourcesChanged = false) { if (ownerSourcesChanged) { PrefabOwnerResolver.Invalidate(); } lock (Sync) { if (!_shutdown) { _dirty = true; _revision++; _nextExistenceCheck = 0f; } } } internal static void Shutdown() { lock (Sync) { _shutdown = true; _dirty = false; _revision++; _nextExistenceCheck = 0f; } } internal static bool NeedsUpdate() { lock (Sync) { if (_shutdown) { return false; } if (_dirty) { return true; } float realtimeSinceStartup = Time.realtimeSinceStartup; if (realtimeSinceStartup < _nextExistenceCheck) { return false; } _nextExistenceCheck = realtimeSinceStartup + 5f; if (File.Exists(ReferencePath)) { return false; } _dirty = true; _revision++; return true; } } internal static bool TryWriteIfNeeded() { return TryWriteIfNeededWithResult() != ItemReferenceWriteResult.NotReady; } internal static ItemReferenceWriteResult TryWriteIfNeededWithResult() { long revision; lock (Sync) { if (_shutdown) { return ItemReferenceWriteResult.ShuttingDown; } if (!_dirty && File.Exists(ReferencePath)) { return ItemReferenceWriteResult.Unchanged; } revision = _revision; } if (!RestrictionEvaluator.TryCreateGuardedItemReferenceSnapshot(out List<GuardedItemReference> entries)) { return ItemReferenceWriteResult.NotReady; } bool flag = WriteIfChanged(BuildContent(entries)); lock (Sync) { _nextExistenceCheck = Time.realtimeSinceStartup + 5f; if (!_shutdown && _revision == revision) { _dirty = false; } } if (!flag) { return ItemReferenceWriteResult.Unchanged; } return ItemReferenceWriteResult.Updated; } private static string BuildContent(IReadOnlyCollection<GuardedItemReference> references) { IReadOnlyDictionary<string, string> owners = PrefabOwnerResolver.Resolve(references.Select((GuardedItemReference entry) => entry.PrefabName)); string value; List<OutputItem> source = references.Select((GuardedItemReference entry) => new OutputItem(entry, owners.TryGetValue(ValheimNameUtils.NormalizePrefabName(entry.PrefabName), out value) ? value : "Unknown / Untracked")).ToList(); StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append(BuildHeader()); foreach (IGrouping<string, OutputItem> item in source.OrderBy((OutputItem item) => PrefabOwnerResolver.GetOwnerSortBucket(item.Owner)).ThenBy<OutputItem, string>((OutputItem item) => item.Owner, StringComparer.OrdinalIgnoreCase).ThenBy<OutputItem, string>((OutputItem item) => item.Owner, StringComparer.Ordinal) .GroupBy<OutputItem, string>((OutputItem item) => item.Owner, StringComparer.OrdinalIgnoreCase)) { stringBuilder.Append("# ===== ").Append(PrefabOwnerResolver.NormalizeOwnerName(item.Key)).AppendLine(" ====="); foreach (IGrouping<string, OutputItem> item2 in item.OrderBy((OutputItem item) => GetItemTypeSortOrder(item.ItemType)).ThenBy<OutputItem, string>((OutputItem item) => item.ItemType, StringComparer.Ordinal).ThenBy((OutputItem item) => (item.RequiredKey.Length != 0) ? 1 : 0) .ThenBy((OutputItem item) => item.RequiredKeyRank) .ThenBy<OutputItem, string>((OutputItem item) => item.RequiredKey, StringComparer.OrdinalIgnoreCase) .ThenBy<OutputItem, string>((OutputItem item) => item.RequiredKey, StringComparer.Ordinal) .ThenBy<OutputItem, string>((OutputItem item) => item.PrefabName, StringComparer.OrdinalIgnoreCase) .ThenBy<OutputItem, string>((OutputItem item) => item.PrefabName, StringComparer.Ordinal) .GroupBy<OutputItem, string>((OutputItem item) => item.ItemType, StringComparer.Ordinal)) { stringBuilder.Append("# ----- ").Append(item2.Key).AppendLine(" -----"); List<string> values = item2.Select((OutputItem item) => (item.RequiredKey.Length != 0) ? (item.PrefabName + ", " + item.RequiredKey) : item.PrefabName).ToList(); AppendYamlSequence(stringBuilder, values); stringBuilder.AppendLine(); } } return stringBuilder.ToString(); } private static int GetItemTypeSortOrder(string itemType) { if (!ItemTypeSortOrder.TryGetValue(itemType, out var value)) { return int.MaxValue; } return value; } private static void AppendYamlSequence(StringBuilder builder, List<string> values) { string text = EntrySerializer.Serialize(values).Replace("\r\n", "\n").TrimEnd(new char[1] { '\n' }); builder.AppendLine(text.Replace("\n", Environment.NewLine)); } private static string BuildHeader() { string newLine = Environment.NewLine; return string.Join(newLine, "# YouAreNotWorthy item restriction reference", "# Generated from the effective loaded runtime. This file is overwritten automatically.", "# It is reference-only: YNW does not read it as configuration and does not ServerSync it.", "# Only the authoritative server/listen host writes it; remote clients leave local copies untouched.", "#", "# Only statically discoverable player-facing item-use targets evaluated by YNW are listed.", "# General equipment, consumables, and ammo require a valid inventory icon, following VNEI's", "# lightweight player-item signal. Explicit Door/OfferingBowl/ItemStand items remain even without one.", "# Prefabs in VNEI 0.17.5's default blacklist are omitted unless their item is directly listed", "# under a progression.yml tier's resources; production-path inheritance is not a direct listing.", "# '- Prefab' means the target currently has no effective required personal key.", "# '- Prefab, key' means that guarded use is blocked while the player lacks that personal key.", "# The key is the final effective result after direct assignment and production-path inheritance.", "# Tier-only resources whose pickup, storage, and crafting are not guarded are omitted.", "# Explicit item-stand boss-item/supported-item declarations are included without an icon;", "# supported-type declarations contribute only icon-backed items.", "# Broad allow-all stands and runtime-only stand changes may be absent", "# unless the item is independently discoverable as equipment, consumable, ammo, Door key, or direct offering.", "# Owner and ItemType headings are presentation-only. ItemType uses the loaded Valheim enum name.", "# Within each ItemType, keyless entries come first. Keyed entries follow the first tier where", "# their requiredKey appears in progression.yml, then prefab name; repeated keys stay together.", "# Owner inference is best-effort; ambiguous or runtime-created prefabs remain Unknown / Untracked.", "#") + newLine; } private static bool WriteIfChanged(string content) { Directory.CreateDirectory(ProgressionConfigLoader.ConfigDirectory); string referencePath = ReferencePath; if (string.Equals(File.Exists(referencePath) ? File.ReadAllText(referencePath) : string.Empty, content, StringComparison.Ordinal)) { return false; } WriteAtomically(referencePath, content); YouAreNotWorthyPlugin.Log.LogInfo((object)("Updated item restriction reference at " + referencePath + ".")); return true; } private static void WriteAtomically(string path, string content) { string text = Path.Combine(Path.GetDirectoryName(path) ?? ProgressionConfigLoader.ConfigDirectory, $".{Path.GetFileName(path)}.{Guid.NewGuid():N}.tmp"); try { File.WriteAllText(text, content, Utf8WithoutBom); if (File.Exists(path)) { File.Replace(text, path, null); } else { File.Move(text, path); } } finally { try { if (File.Exists(text)) { File.Delete(text); } } catch (Exception) { } } } } internal static class ItemReferenceCommands { [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleOptionsFetcher <>9__9_0; internal List<string> <RegisterConsoleCommand>b__9_0() { return TabOptions; } } private const string CommandName = "ynw:items"; private const string RpcRefreshRequest = "YNW_AdminItemReferenceRefresh"; private const string RpcRefreshResult = "YNW_AdminItemReferenceResult"; private const int MaxResultLength = 2048; private const int MaxPeerNameLength = 128; private static readonly List<string> TabOptions = new List<string> { "refresh" }; private static readonly FieldInfo? TerminalCommandsField = typeof(Terminal).GetField("commands", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); private static ConsoleCommand? _consoleCommand; private static bool _shutdown; internal static void RegisterConsoleCommand() { //IL_0055: Unknown result type (might be due to invalid IL or missing references) //IL_0086: Expected O, but got Unknown //IL_0081: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //IL_0073: Unknown result type (might be due to invalid IL or missing references) //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Expected O, but got Unknown _shutdown = false; if (_consoleCommand != null) { return; } Dictionary<string, ConsoleCommand> terminalCommands = GetTerminalCommands(); if (terminalCommands == null) { YouAreNotWorthyPlugin.Log.LogWarning((object)"Could not inspect Valheim's console-command registry; 'ynw:items' was not registered."); return; } if (terminalCommands.ContainsKey("ynw:items")) { YouAreNotWorthyPlugin.Log.LogWarning((object)"Could not register 'ynw:items' because another console command already uses that name."); return; } ConsoleEvent val = HandleConsoleCommand; object obj = <>c.<>9__9_0; if (obj == null) { ConsoleOptionsFetcher val2 = () => TabOptions; <>c.<>9__9_0 = val2; obj = (object)val2; } _consoleCommand = new ConsoleCommand("ynw:items", "Refresh the authoritative server items.reference.yml. Usage: ynw:items refresh", val, false, true, false, false, false, (ConsoleOptionsFetcher)obj, false, false, false); } internal static void RegisterPeer(ZNet net, ZNetPeer peer) { if (_shutdown) { return; } try { if (net.IsServer()) { peer.m_rpc.Register<ZPackage>("YNW_AdminItemReferenceRefresh", (Action<ZRpc, ZPackage>)RPC_RefreshRequest); } else { peer.m_rpc.Register<string>("YNW_AdminItemReferenceResult", (Action<ZRpc, string>)RPC_RefreshResult); } } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogError((object)$"Failed to register YNW item-reference RPCs: {arg}"); } } internal static void Shutdown() { _shutdown = true; Dictionary<string, ConsoleCommand> terminalCommands = GetTerminalCommands(); if (_consoleCommand != null && terminalCommands != null && terminalCommands.TryGetValue("ynw:items", out var value) && value == _consoleCommand) { terminalCommands.Remove("ynw:items"); } _consoleCommand = null; } private static Dictionary<string, ConsoleCommand>? GetTerminalCommands() { try { return TerminalCommandsField?.GetValue(null) as Dictionary<string, ConsoleCommand>; } catch (Exception) { return null; } } private static void HandleConsoleCommand(ConsoleEventArgs args) { //IL_00d2: Unknown result type (might be due to invalid IL or missing references) //IL_00d8: Expected O, but got Unknown if (_shutdown) { return; } if (args.Length != 2 || !string.Equals(args[1], "refresh", StringComparison.OrdinalIgnoreCase)) { Terminal context = args.Context; if (context != null) { context.AddString("Usage: ynw:items refresh"); } return; } ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null) { Terminal context2 = args.Context; if (context2 != null) { context2.AddString("YNW item-reference commands require an active server session."); } return; } if (instance.IsServer()) { ExecuteRefresh(null, args.Context); return; } if (!YouAreNotWorthyPlugin.IsLocalAdmin) { Terminal context3 = args.Context; if (context3 != null) { context3.AddString("You are not an admin on this server."); } return; } ZNetPeer serverPeer = instance.GetServerPeer(); if (serverPeer == null || !serverPeer.m_rpc.IsConnected()) { Terminal context4 = args.Context; if (context4 != null) { context4.AddString("The server connection is not ready."); } return; } serverPeer.m_rpc.Invoke("YNW_AdminItemReferenceRefresh", new object[1] { (object)new ZPackage() }); Terminal context5 = args.Context; if (context5 != null) { context5.AddString("Requested an item-reference refresh from the server."); } } private static void RPC_RefreshRequest(ZRpc requester, ZPackage _) { if (!_shutdown && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { if (!IsRemoteAdmin(requester)) { YouAreNotWorthyPlugin.Log.LogWarning((object)("Rejected a YNW item-reference refresh from non-admin peer '" + GetPeerHostName(requester) + "'.")); SendResult(requester, null, "You are not an admin on this server."); } else { ExecuteRefresh(requester, null); } } } private static void ExecuteRefresh(ZRpc? requester, Terminal? terminal) { if (!_shutdown) { string text = ((requester == null) ? "local-server" : GetPeerHostName(requester)); YouAreNotWorthyPlugin.Log.LogInfo((object)("Admin '" + text + "' requested an authoritative item-reference refresh.")); SendResult(requester, terminal, KeyReferenceWriter.TryRefreshItemReference(YouAreNotWorthyPlugin.IsSourceOfTruth) switch { ItemReferenceRefreshResult.Updated => "Updated the server item reference from the effective runtime: " + ItemReferenceWriter.ReferencePath, ItemReferenceRefreshResult.Unchanged => "The server item reference already matches the effective runtime: " + ItemReferenceWriter.ReferencePath, ItemReferenceRefreshResult.NotAuthoritative => "YNW item references can be refreshed only by the authoritative server.", ItemReferenceRefreshResult.RuntimeNotReady => "The server runtime is not ready for an item-reference scan. Try again after the world finishes loading.", ItemReferenceRefreshResult.ShuttingDown => "YNW is shutting down; the item reference was not refreshed.", _ => "The server could not refresh the item reference. Check the server log; an automatic retry remains queued.", }); } } private static void RPC_RefreshResult(ZRpc server, string message) { if (!_shutdown) { if (!IsServerConnection(server)) { YouAreNotWorthyPlugin.Log.LogWarning((object)"Ignored a YNW item-reference result from a non-server connection."); } else { PrintConsole(SanitizeResult(message)); } } } private static void SendResult(ZRpc? requester, Terminal? terminal, string message) { string text = SanitizeResult(message); YouAreNotWorthyPlugin.Log.LogInfo((object)text); if (requester == null) { if (terminal != null) { terminal.AddString(text); } return; } try { requester.Invoke("YNW_AdminItemReferenceResult", new object[1] { text }); } catch (Exception ex) { YouAreNotWorthyPlugin.Log.LogWarning((object)("Failed to return a YNW item-reference result: " + ex.Message)); } } private static bool IsRemoteAdmin(ZRpc requester) { try { ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? instance.GetPeer(requester) : null); return (Object)(object)instance != (Object)null && instance.IsServer() && val != null && val.IsReady() && val.m_rpc == requester && instance.IsAdmin(requester.GetSocket().GetHostName()); } catch (Exception) { return false; } } private static bool IsServerConnection(ZRpc rpc) { try { ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? instance.GetServerPeer() : null); return val != null && val.m_server && val.m_rpc == rpc; } catch (Exception) { return false; } } private static string GetPeerHostName(ZRpc rpc) { try { string text = rpc.GetSocket().GetHostName() ?? string.Empty; StringBuilder stringBuilder = new StringBuilder(Math.Min(text.Length, 128)); for (int i = 0; i < text.Length && i < 128; i++) { char c = text[i]; stringBuilder.Append(char.IsControl(c) ? ' ' : c); } return (stringBuilder.Length == 0) ? "unknown" : stringBuilder.ToString(); } catch (Exception) { return "unknown"; } } private static string SanitizeResult(string? message) { string text = (message ?? string.Empty).Replace('\r', ' ').Replace('\n', ' '); if (text.Length > 2048) { return text.Substring(0, 2048); } return text; } private static void PrintConsole(string message) { if ((Object)(object)Console.instance != (Object)null) { ((Terminal)Console.instance).AddString(message); } } } internal enum ItemReferenceRefreshResult { Updated, Unchanged, NotAuthoritative, RuntimeNotReady, ShuttingDown, Failed } internal static class KeyReferenceWriter { private readonly struct ParsedGlobalKey { internal string Identity { get; } internal string Spelling { get; } internal bool HasValue { get; } internal ParsedGlobalKey(string identity, string spelling, bool hasValue) { Identity = identity; Spelling = spelling; HasValue = hasValue; } } private sealed class ObservedGlobalKey { private readonly HashSet<string> _spellings = new HashSet<string>(StringComparer.Ordinal); internal string Identity { get; } internal bool HasValue { get; private set; } internal ObservedGlobalKey(string identity) { Identity = identity; } internal bool Add(string spelling, bool hasValue) { bool result = _spellings.Add(spelling); if (hasValue && !HasValue) { HasValue = true; result = true; } return result; } internal ObservedGlobalKeySnapshot Snapshot() { return new ObservedGlobalKeySnapshot(Identity, _spellings.OrderBy<string, string>((string value) => value, StringComparer.Ordinal).ToList(), HasValue); } } private sealed class ObservedGlobalKeySnapshot { internal string Identity { get; } internal List<string> Spellings { get; } internal bool HasValue { get; } internal ObservedGlobalKeySnapshot(string identity, List<string> spellings, bool hasValue) { Identity = identity; Spellings = spellings; HasValue = hasValue; } } private sealed class GlobalKeyBuilder { private readonly Dictionary<string, NameCandidate> _names = new Dictionary<string, NameCandidate>(StringComparer.Ordinal); internal string Identity { get; } internal bool HasValue { get; set; } internal HashSet<string> DefeatPrefabs { get; } = new HashSet<string>(StringComparer.Ordinal); internal GlobalKeyBuilder(string identity) { Identity = identity; } internal void AddName(string name, int priority, bool isWorldFallback) { if (!_names.TryGetValue(name, out var value) || priority < value.Priority || (value.IsWorldFallback && !isWorldFallback)) { _names[name] = new NameCandidate(priority, isWorldFallback); } } internal GlobalKeyReferenceEntry Build() { string canonicalName = (from entry in _names orderby entry.Value.Priority, entry.Value.IsWorldFallback select entry).ThenBy<KeyValuePair<string, NameCandidate>, string>((KeyValuePair<string, NameCandidate> entry) => entry.Key, StringComparer.Ordinal).FirstOrDefault().Key ?? Identity; List<string> list = (from entry in _names where !entry.Value.IsWorldFallback && !string.Equals(entry.Key, canonicalName, StringComparison.Ordinal) select entry.Key).OrderBy<string, string>((string value) => value, StringComparer.OrdinalIgnoreCase).ThenBy<string, string>((string value) => value, StringComparer.Ordinal).ToList(); List<string> list2 = DefeatPrefabs.OrderBy<string, string>((string value) => value, StringComparer.OrdinalIgnoreCase).ThenBy<string, string>((string value) => value, StringComparer.Ordinal).ToList(); return new GlobalKeyReferenceEntry { Key = canonicalName, Handling = ((HasValue || ProgressionIndex.IsSharedWorldKey(canonicalName)) ? "sharedWorld" : "personalized"), Aliases = ((list.Count > 0) ? list : null), DefeatPrefabs = ((list2.Count > 0) ? list2 : null) }; } } private readonly struct NameCandidate { internal int Priority { get; } internal bool IsWorldFallback { get; } internal NameCandidate(int priority, bool isWorldFallback) { Priority = priority; IsWorldFallback = isWorldFallback; } } private sealed class KeyReferenceDocument { [YamlMember(Order = 1)] public List<GlobalKeyReferenceEntry> GlobalKeys { get; set; } = new List<GlobalKeyReferenceEntry>(); [YamlMember(Order = 2)] public List<string> PlayerBasedRaidKeys { get; set; } = new List<string>(); } private sealed class GlobalKeyReferenceEntry { [YamlMember(Order = 1)] public string Key { get; set; } = string.Empty; [YamlMember(Order = 2)] public string Handling { get; set; } = string.Empty; [YamlMember(Order = 3)] public List<string>? Aliases { get; set; } [YamlMember(Order = 4)] public List<string>? DefeatPrefabs { get; set; } } internal const string ReferenceFileName = "keys.reference.yml"; private const float ObservationDebounceSeconds = 1f; private const float NotReadyRetrySeconds = 1f; private const float FailureRetrySeconds = 5f; private const string PersonalizedHandling = "personalized"; private const string SharedWorldHandling = "sharedWorld"; private const int EnumNamePriority = 0; private const int DefeatKeyPriority = 1; private const int ObservedNamePriority = 2; private const int RaidNamePriority = 3; private const int WorldFallbackPriority = 4; private static readonly UTF8Encoding Utf8WithoutBom = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); private static readonly ISerializer Serializer = new SerializerBuilder().WithNamingConvention(CamelCaseNamingConvention.Instance).ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull | DefaultValuesHandling.OmitDefaults).Build(); private static readonly object Sync = new object(); private static readonly Dictionary<string, ObservedGlobalKey> ObservedGlobalKeys = new Dictionary<string, ObservedGlobalKey>(StringComparer.OrdinalIgnoreCase); private static bool _dirty = true; private static bool _shutdown; private static bool _runtimeReady; private static bool _wasSourceOfTruth; private static long _revision = 1L; private static float _updateAfter; private static float _retryAfter; private static ZoneSystem? _lastZoneSystem; private static ZNetScene? _lastNetScene; private static RandEventSystem? _lastEventSystem; private static ObjectDB? _lastObjectDb; internal static string ReferencePath => Path.Combine(ProgressionConfigLoader.ConfigDirectory, "keys.reference.yml"); internal static void ObserveGlobalKey(string? rawKey) { if (!TryParseGlobalKey(rawKey, out var parsed)) { return; } lock (Sync) { if (!_shutdown) { if (!ObservedGlobalKeys.TryGetValue(parsed.Identity, out ObservedGlobalKey value)) { value = new ObservedGlobalKey(parsed.Identity); ObservedGlobalKeys[parsed.Identity] = value; } if (value.Add(parsed.Spelling, parsed.HasValue)) { _dirty = true; _revision++; _updateAfter = Time.realtimeSinceStartup + 1f; } } } } internal static void Reset() { ItemReferenceWriter.Reset(); lock (Sync) { ObservedGlobalKeys.Clear(); _shutdown = false; _runtimeReady = false; _wasSourceOfTruth = false; _dirty = true; _revision++; _updateAfter = 0f; _retryAfter = 0f; _lastZoneSystem = null; _lastNetScene = null; _lastEventSystem = null; _lastObjectDb = null; } } internal static void MarkRuntimeReady() { lock (Sync) { if (!_shutdown) { _runtimeReady = true; MarkDirtyLocked(); } } } internal static void MarkDirty() { lock (Sync) { if (!_shutdown) { MarkDirtyLocked(); } } } internal static void MarkItemReferenceDirty(bool ownerSourcesChanged = false) { ItemReferenceWriter.MarkDirty(ownerSourcesChanged); MarkDirty(); } internal static ItemReferenceRefreshResult TryRefreshItemReference(bool isSourceOfTruth) { if (!isSourceOfTruth || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return ItemReferenceRefreshResult.NotAuthoritative; } lock (Sync) { if (_shutdown) { return ItemReferenceRefreshResult.ShuttingDown; } } try { RestrictionEvaluator.InvalidateItemCache(); ItemReferenceWriter.MarkDirty(ownerSourcesChanged: true); if (!IsItemReferenceRuntimeReady()) { return ItemReferenceRefreshResult.RuntimeNotReady; } return ItemReferenceWriter.TryWriteIfNeededWithResult() switch { ItemReferenceWriteResult.Updated => ItemReferenceRefreshResult.Updated, ItemReferenceWriteResult.Unchanged => ItemReferenceRefreshResult.Unchanged, ItemReferenceWriteResult.NotReady => ItemReferenceRefreshResult.RuntimeNotReady, ItemReferenceWriteResult.ShuttingDown => ItemReferenceRefreshResult.ShuttingDown, _ => ItemReferenceRefreshResult.Failed, }; } catch (Exception ex) { lock (Sync) { if (!_shutdown) { MarkDirtyLocked(); _retryAfter = Time.realtimeSinceStartup + 5f; } } YouAreNotWorthyPlugin.Log.LogWarning((object)("Failed to refresh the item restriction reference: " + ex.GetType().Name + ": " + ex.Message + ". " + $"Retrying automatically in {5f:0.#}s.")); return ItemReferenceRefreshResult.Failed; } } private static bool IsItemReferenceRuntimeReady() { ObjectDB instance = ObjectDB.instance; ZNetScene instance2 = ZNetScene.instance; if (_runtimeReady && (Object)(object)instance != (Object)null && instance.m_items != null && instance.m_items.Count > 0 && instance.m_recipes != null && (Object)(object)instance2 != (Object)null) { return ValheimNameUtils.EnumerateRegisteredPrefabs(instance2).Any(); } return false; } internal static void Shutdown() { ItemReferenceWriter.Shutdown(); lock (Sync) { ObservedGlobalKeys.Clear(); _shutdown = true; _runtimeReady = false; _wasSourceOfTruth = false; _dirty = false; _revision++; _updateAfter = 0f; _retryAfter = 0f; _lastZoneSystem = null; _lastNetScene = null; _lastEventSystem = null; _lastObjectDb = null; } } internal static void TryUpdate(bool isSourceOfTruth) { bool flag = isSourceOfTruth && (Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer(); ZoneSystem instance = ZoneSystem.instance; ZNetScene instance2 = ZNetScene.instance; RandEventSystem instance3 = RandEventSystem.instance; ObjectDB instance4 = ObjectDB.instance; bool flag2 = flag && ItemReferenceWriter.NeedsUpdate(); lock (Sync) { if (_shutdown) { return; } if (flag && !_wasSourceOfTruth) { MarkDirtyLocked(); } _wasSourceOfTruth = flag; if (_lastZoneSystem != instance || _lastNetScene != instance2 || _lastEventSystem != instance3 || _lastObjectDb != instance4) { _lastZoneSystem = instance; _lastNetScene = instance2; _lastEventSystem = instance3; _lastObjectDb = instance4; ItemReferenceWriter.MarkDirty(ownerSourcesChanged: true); MarkDirtyLocked(); } else if (flag2 && !_dirty) { MarkDirtyLocked(); } } if (!flag) { return; } float realtimeSinceStartup = Time.realtimeSinceStartup; long revision; List<ObservedGlobalKeySnapshot> observed; lock (Sync) { if (_shutdown || !_dirty || realtimeSinceStartup < _updateAfter || realtimeSinceStartup < _retryAfter) { return; } revision = _revision; observed = ObservedGlobalKeys.Values.Select((ObservedGlobalKey key) => key.Snapshot()).ToList(); } if (!TryGetReadyRuntime(out ZoneSystem zoneSystem, out ZNetScene netScene, out RandEventSystem eventSystem, out ObjectDB _)) { lock (Sync) { if (!_shutdown && _revision == revision) { _updateAfter = realtimeSinceStartup + 1f; } return; } } try { WriteIfChanged(BuildContent(zoneSystem, netScene, eventSystem, observed)); if (!ItemReferenceWriter.TryWriteIfNeeded()) { lock (Sync) { if (!_shutdown && _revision == revision) { _updateAfter = realtimeSinceStartup + 1f; } return; } } lock (Sync) { _retryAfter = 0f; if (!_shutdown && _revision == revision) { _dirty = false; } } } catch (Exception ex) { lock (Sync) { if (!_shutdown) { _dirty = true; _retryAfter = Time.realtimeSinceStartup + 5f; } } YouAreNotWorthyPlugin.Log.LogWarning((object)("Failed to update runtime references: " + ex.GetType().Name + ": " + ex.Message + ". " + $"Retrying in {5f:0.#}s.")); } } private static bool TryGetReadyRuntime(out ZoneSystem zoneSystem, out ZNetScene netScene, out RandEventSystem eventSystem, out ObjectDB objectDb) { zoneSystem = ZoneSystem.instance; netScene = ZNetScene.instance; eventSystem = RandEventSystem.instance; objectDb = ObjectDB.instance; if ((Object)(object)zoneSystem != (Object)null && (Object)(object)netScene != (Object)null && (Object)(object)eventSystem != (Object)null && (Object)(object)objectDb != (Object)null && _runtimeReady && zoneSystem.m_globalKeysValues != null && ValheimNameUtils.EnumerateRegisteredPrefabs(netScene).Any() && eventSystem.m_events != null && eventSystem.m_events.Count > 0 && objectDb.m_items != null && objectDb.m_items.Count > 0) { return objectDb.m_recipes != null; } return false; } private static string BuildContent(ZoneSystem zoneSystem, ZNetScene netScene, RandEventSystem eventSystem, IEnumerable<ObservedGlobalKeySnapshot> observed) { Dictionary<string, GlobalKeyBuilder> dictionary = new Dictionary<string, GlobalKeyBuilder>(StringComparer.OrdinalIgnoreCase); HashSet<string> hashSet = new HashSet<string>(StringComparer.Ordinal); AddEnumGlobalKeys(dictionary); AddWorldFallbackKeys(zoneSystem, dictionary); AddRaidKeys(eventSystem, dictionary, hashSet); AddDefeatKeys(netScene, dictionary); AddObservedKeys(observed, dictionary); KeyReferenceDocument graph = new KeyReferenceDocument { GlobalKeys = (from key in dictionary.Values select key.Build() into key orderby string.Equals(key.Handling, "sharedWorld", StringComparison.Ordinal) ? 1 : 0 select key).ThenBy<GlobalKeyReferenceEntry, string>((GlobalKeyReferenceEntry key) => key.Key, StringComparer.OrdinalIgnoreCase).ThenBy<GlobalKeyReferenceEntry, string>((GlobalKeyReferenceEntry key) => key.Key, StringComparer.Ordinal).ToList(), PlayerBasedRaidKeys = hashSet.OrderBy<string, string>((string key) => key, StringComparer.OrdinalIgnoreCase).ThenBy<string, string>((string key) => key, StringComparer.Ordinal).ToList() }; return BuildHeader() + Serializer.Serialize(graph); } private static void AddEnumGlobalKeys(Dictionary<string, GlobalKeyBuilder> globalKeys) { //IL_001d: Unknown result type (might be due to invalid IL or missing references) //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Invalid comparison between Unknown and I4 //IL_0028: Unknown result type (might be due to invalid IL or missing references) //IL_002b: Invalid comparison between Unknown and I4 foreach (GlobalKeys value in Enum.GetValues(typeof(GlobalKeys))) { if ((int)value != 32 && (int)value != 43) { AddGlobalKey(globalKeys, ((object)value/*cast due to .constrained prefix*/).ToString(), 0); } } } private static void AddWorldFallbackKeys(ZoneSystem zoneSystem, Dictionary<string, GlobalKeyBuilder> globalKeys) { foreach (KeyValuePair<string, string> globalKeysValue in zoneSystem.m_globalKeysValues) { AddGlobalKey(globalKeys, globalKeysValue.Key, 4, !string.IsNullOrEmpty(globalKeysValue.Value), isWorldFallback: true); } } private static void AddRaidKeys(RandEventSystem eventSystem, Dictionary<string, GlobalKeyBuilder> globalKeys, HashSet<string> playerBasedRaidKeys) { foreach (RandomEvent @event in eventSystem.m_events) { if (@event != null) { AddGlobalKeys(globalKeys, @event.m_requiredGlobalKeys, 3); AddGlobalKeys(globalKeys, @event.m_notRequiredGlobalKeys, 3); AddPersonalKeys(playerBasedRaidKeys, @event.m_altRequiredPlayerKeysAny); AddPersonalKeys(playerBasedRaidKeys, @event.m_altRequiredPlayerKeysAll); AddPersonalKeys(playerBasedRaidKeys, @event.m_altNotRequiredPlayerKeys); } } } private static void AddDefeatKeys(ZNetScene netScene, Dictionary<string, GlobalKeyBuilder> globalKeys) { foreach (GameObject item in ValheimNameUtils.EnumerateRegisteredPrefabs(netScene)) { Character component = item.GetComponent<Character>(); if (!((Object)(object)component == (Object)null) && TryParseGlobalKey(component.m_defeatSetGlobalKey, out var parsed)) { GlobalKeyBuilder orCreate = GetOrCreate(globalKeys, parsed.Identity); orCreate.AddName(parsed.Spelling, 1, isWorldFallback: false); orCreate.HasValue |= parsed.HasValue; string prefabName = ValheimNameUtils.GetPrefabName(item); if (prefabName.Length > 0) { orCreate.DefeatPrefabs.Add(prefabName); } } } } private static void AddObservedKeys(IEnumerable<ObservedGlobalKeySnapshot> observed, Dictionary<string, GlobalKeyBuilder> globalKeys) { foreach (ObservedGlobalKeySnapshot item in observed) { GlobalKeyBuilder orCreate = GetOrCreate(globalKeys, item.Identity); foreach (string spelling in item.Spellings) { orCreate.AddName(spelling, 2, isWorldFallback: false); } orCreate.HasValue |= item.HasValue; } } private static void AddGlobalKeys(Dictionary<string, GlobalKeyBuilder> globalKeys, IEnumerable<string>? keys, int priority) { if (keys == null) { return; } foreach (string key in keys) { AddGlobalKey(globalKeys, key, priority); } } private static void AddPersonalKeys(HashSet<string> personalKeys, IEnumerable<string>? keys) { if (keys == null) { return; } foreach (string key in keys) { string text = (key ?? string.Empty).Trim(); if (text.Length > 0) { personalKeys.Add(text); } } } private static void AddGlobalKey(Dictionary<string, GlobalKeyBuilder> globalKeys, string? rawKey, int priority, bool hasValue = false, bool isWorldFallback = false) { if (TryParseGlobalKey(rawKey, out var parsed)) { GlobalKeyBuilder orCreate = GetOrCreate(globalKeys, parsed.Identity); orCreate.AddName(parsed.Spelling, priority, isWorldFallback); orCreate.HasValue |= hasValue || parsed.HasValue; } } private static GlobalKeyBuilder GetOrCreate(Dictionary<string, GlobalKeyBuilder> globalKeys, string identity) { if (!globalKeys.TryGetValue(identity, out GlobalKeyBuilder value)) { value = (globalKeys[identity] = new GlobalKeyBuilder(identity)); } return value; } private static bool TryParseGlobalKey(string? rawKey, out ParsedGlobalKey parsed) { //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0089: Unknown result type (might be due to invalid IL or missing references) //IL_008d: Invalid comparison between Unknown and I4 //IL_008f: Unknown result type (might be due to invalid IL or missing references) //IL_0093: Invalid comparison between Unknown and I4 string text = (rawKey ?? string.Empty).Trim(); if (text.Length == 0) { parsed = default(ParsedGlobalKey); return false; } string value = default(string); GlobalKeys val = default(GlobalKeys); string keyValue = ZoneSystem.GetKeyValue(text, ref value, ref val); int num = text.IndexOf(' '); string text2 = ((num > 0) ? text.Substring(0, num) : text).Trim(); if (keyValue.Length == 0 || text2.Length == 0) { parsed = default(ParsedGlobalKey); return false; } if (Enum.TryParse<GlobalKeys>(text2, ignoreCase: true, out GlobalKeys result) && Enum.IsDefined(typeof(GlobalKeys), result) && ((int)result == 32 || (int)result == 43)) { parsed = default(ParsedGlobalKey); return false; } parsed = new ParsedGlobalKey(keyValue, text2, !string.IsNullOrEmpty(value)); return true; } private static string BuildHeader() { string newLine = Environment.NewLine; return string.Join(newLine, "# YouAreNotWorthy key reference", "# Generated from the effective loaded runtime. This file is overwritten automatically.", "# It is reference-only: YNW does not read it as configuration and does not ServerSync it.", "# Only the authoritative server/listen host writes it; remote clients leave local copies untouched.", "# This is a discovered catalog, not a world/player progress snapshot; values and presence are omitted.", "# globalKeys lists personalized entries first, followed by sharedWorld entries.", "#", "# YNW personalizes every value-less, non-reserved global key. Its world write is blocked,", $"# the same literal is granted as a native character unique key to active players within {32f:0.##}m", "# of the event position, and ordinary global-key reads are evaluated against that character key.", "# Value-bearing keys and reserved modifiers/runtime state remain shared world keys.", "#", "# PlayerEvents OFF: raids use requiredGlobalKeys and forbiddenGlobalKeys. YNW evaluates", "# personalizable entries per character while shared entries remain world conditions.", "# PlayerEvents ON: when any alternate known-item/player-key list is non-empty, Vanilla uses", "# those character conditions instead. With all alternate lists empty, global conditions remain active.", "# playerBasedRaidKeys contains only native unique-key literals; known-item conditions are omitted.", "# Different literals are not aliases. Global keys are grouped case-insensitively, while player keys", "# retain exact casing. aliases records additional loaded spellings of the same global-key identity.", "# defeatPrefabs records only root Character.m_defeatSetGlobalKey setters.", "# Runtime fields and observed Get/Set calls are discoverable; a mod's dormant hard-coded key may", "# appear only after that code path runs. Reload the world after adding or removing content mods.", "#") + newLine; } private static void MarkDirtyLocked() { _dirty = true; _revision++; _updateAfter = Time.realtimeSinceStartup + 1f; _retryAfter = 0f; } private static void WriteIfChanged(string content) { Directory.CreateDirectory(ProgressionConfigLoader.ConfigDirectory); string referencePath = ReferencePath; if (!string.Equals(File.Exists(referencePath) ? File.ReadAllText(referencePath) : string.Empty, content, StringComparison.Ordinal)) { File.WriteAllText(referencePath, content, Utf8WithoutBom); YouAreNotWorthyPlugin.Log.LogInfo((object)("Updated key reference at " + referencePath + ".")); } } } internal static class PersonalKeySnapshot { private const string SnapshotZdoKey = "YNW_PersonalKeys"; private const int SnapshotMagic = 827805273; private const byte SnapshotVersion = 1; private const int MaxSnapshotKeys = 4096; private const int MaxSnapshotBytes = 1048576; internal static void PublishLocal() { Player localPlayer = Player.m_localPlayer; if (!((Object)(object)localPlayer == (Object)null) && ((Character)localPlayer).IsOwner() && TryGetPlayerZdo(localPlayer, out ZDO zdo)) { List<string> uniqueKeys = localPlayer.GetUniqueKeys(); uniqueKeys.RemoveAll((string key) => string.IsNullOrWhiteSpace(key)); uniqueKeys.Sort(StringComparer.Ordinal); if (uniqueKeys.Count > 4096) { YouAreNotWorthyPlugin.Log.LogWarning((object)$"Personal-key snapshot contains {uniqueKeys.Count} keys; only the first {4096} will be published."); uniqueKeys.RemoveRange(4096, uniqueKeys.Count - 4096); } int publishedCount; byte[] array = Serialize(uniqueKeys, out publishedCount); if (publishedCount < uniqueKeys.Count) { YouAreNotWorthyPlugin.Log.LogWarning((object)($"Personal-key snapshot exceeded {1048576} bytes; " + $"only the first {publishedCount} of {uniqueKeys.Count} keys will be published.")); } if (!ByteArraysEqual(zdo.GetByteArray("YNW_PersonalKeys", Array.Empty<byte>()), array)) { zdo.Set("YNW_PersonalKeys", array); } } } internal static bool TryHas(Player? player, string? key, out bool hasKey) { hasKey = false; if ((Object)(object)player == (Object)null || !ProgressionIndex.TryGetCanonicalPersonalKey(key, out string canonicalKey)) { return false; } if ((Object)(object)Player.m_localPlayer != (Object)null && (Object)(object)player == (Object)(object)Player.m_localPlayer) { hasKey = PlayerKeys.HasNativeKey(player, canonicalKey); return true; } if (!TryGetPlayerZdo(player, out ZDO zdo)) { return false; } return TryHas(zdo, canonicalKey, out hasKey); } internal static bool TryHas(ZDO? zdo, string? key, out bool hasKey) { hasKey = false; if (zdo == null || !ProgressionIndex.TryGetCanonicalPersonalKey(key, out string canonicalKey)) { return false; } return TryRead(zdo.GetByteArray("YNW_PersonalKeys", Array.Empty<byte>()), canonicalKey, out hasKey); } private static byte[] Serialize(List<string> keys, out int publishedCount) { using MemoryStream memoryStream = new MemoryStream(); using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8, leaveOpen: true)) { binaryWriter.Write(827805273); binaryWriter.Write((byte)1); long position = memoryStream.Position; binaryWriter.Write(0); publishedCount = 0; foreach (string key in keys) { long position2 = memoryStream.Position; binaryWriter.Write(key); binaryWriter.Flush(); if (memoryStream.Length > 1048576) { memoryStream.SetLength(position2); memoryStream.Position = position2; break; } publishedCount++; } long position3 = memoryStream.Position; memoryStream.Position = position; binaryWriter.Write(publishedCount); memoryStream.Position = position3; } return memoryStream.ToArray(); } private static bool TryGetPlayerZdo(Player player, out ZDO zdo) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_0009: 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) zdo = null; ZDOID zDOID = ((Character)player).GetZDOID(); ZDOMan instance = ZDOMan.instance; if (((ZDOID)(ref zDOID)).IsNone() || instance == null) { return false; } zdo = instance.GetZDO(zDOID); return zdo != null; } private static bool TryRead(byte[] payload, string canonicalKey, out bool hasKey) { hasKey = false; if (payload.Length == 0 || payload.Length > 1048576) { return false; } try { using MemoryStream memoryStream = new MemoryStream(payload, writable: false); using BinaryReader binaryReader = new BinaryReader(memoryStream, Encoding.UTF8, leaveOpen: false); if (binaryReader.ReadInt32() != 827805273 || binaryReader.ReadByte() != 1) { return false; } int num = binaryReader.ReadInt32(); if (num < 0 || num > 4096) { return false; } for (int i = 0; i < num; i++) { if (string.Equals(binaryReader.ReadString(), canonicalKey, StringComparison.OrdinalIgnoreCase)) { hasKey = true; } } return memoryStream.Position == memoryStream.Length; } catch (Exception) { return false; } } private static bool ByteArraysEqual(byte[] left, byte[] right) { if (left.Length != right.Length) { return false; } for (int i = 0; i < left.Length; i++) { if (left[i] != right[i]) { return false; } } return true; } } internal static class PlayerKeyCommands { private enum AdminOperation : byte { Players = 1, List, Add, Remove } private enum TargetResult : byte { Added = 1, AlreadyPresent, Removed, NotPresent, InvalidKey, NoLocalPlayer, CharacterChanged, Listed, Failed } private sealed class OnlineTarget { internal string SteamId { get; } internal string PlayerName { get; } internal ZDOID CharacterId { get; } internal ZRpc? Rpc { get; } internal bool IsLocal => Rpc == null; internal OnlineTarget(string steamId, string playerName, ZDOID characterId, ZRpc? rpc) { //IL_0015: Unknown result type (might be due to invalid IL or missing references) //IL_0016: Unknown result type (might be due to invalid IL or missing references) SteamId = steamId; PlayerName = playerName; CharacterId = characterId; Rpc = rpc; } } private sealed class PendingRequest { internal ZRpc? Requester { get; } internal ZRpc Target { get; } internal AdminOperation Operation { get; } internal string SteamId { get; } internal string PlayerName { get; } internal string Key { get; } internal float Deadline { get; } internal PendingRequest(ZRpc? requester, ZRpc target, AdminOperation operation, string steamId, string playerName, string key, float deadline) { Requester = requester; Target = target; Operation = operation; SteamId = steamId; PlayerName = playerName; Key = key; Deadline = deadline; } } [Serializable] [CompilerGenerated] private sealed class <>c { public static readonly <>c <>9 = new <>c(); public static ConsoleOptionsFetcher <>9__20_0; public static Predicate<string> <>9__34_0; public static Comparison<OnlineTarget> <>9__41_0; internal List<string> <RegisterConsoleCommand>b__20_0() { return TabOptions; } internal bool <WriteKeyListResponse>b__34_0(string key) { return string.IsNullOrWhiteSpace(key); } internal int <BuildOnlinePlayersMessage>b__41_0(OnlineTarget left, OnlineTarget right) { return string.Compare(left.SteamId, right.SteamId, StringComparison.Ordinal); } } private const string CommandName = "ynw:keys"; private const string RpcAdminRequest = "YNW_AdminKeyRequest"; private const string RpcAdminResult = "YNW_AdminKeyResult"; private const string RpcTargetRequest = "YNW_AdminKeyTargetRequest"; private const string RpcTargetResult = "YNW_AdminKeyTargetResult"; private const float RequestTimeoutSeconds = 10f; private const int MaxKeyLength = 256; private const int MaxListedKeys = 512; private const int MaxListCharacters = 16384; private const int MaxPendingRequests = 64; private const int MaxPendingRequestsPerAdmin = 8; private static readonly List<string> TabOptions = new List<string> { "players", "list", "add", "remove" }; private static readonly Dictionary<long, PendingRequest> PendingRequests = new Dictionary<long, PendingRequest>(); private static readonly FieldInfo? TerminalCommandsField = typeof(Terminal).GetField("commands", BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); private static ConsoleCommand? _consoleCommand; private static long _nextRequestToken; internal static void RegisterConsoleCommand() { //IL_004f: Unknown result type (might be due to invalid IL or missing references) //IL_0080: Expected O, but got Unknown //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_0085: Expected O, but got Unknown //IL_006d: 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) //IL_0078: Expected O, but got Unknown if (_consoleCommand != null) { return; } Dictionary<string, ConsoleCommand> terminalCommands = GetTerminalCommands(); if (terminalCommands == null) { YouAreNotWorthyPlugin.Log.LogWarning((object)"Could not inspect Valheim's console-command registry; 'ynw:keys' was not registered."); return; } if (terminalCommands.ContainsKey("ynw:keys")) { YouAreNotWorthyPlugin.Log.LogWarning((object)"Could not register 'ynw:keys' because another console command already uses that name."); return; } ConsoleEvent val = HandleConsoleCommand; object obj = <>c.<>9__20_0; if (obj == null) { ConsoleOptionsFetcher val2 = () => TabOptions; <>c.<>9__20_0 = val2; obj = (object)val2; } _consoleCommand = new ConsoleCommand("ynw:keys", "Manage native personal keys for an online Steam player. Usage: ynw:keys players|list|add|remove", val, false, true, false, false, false, (ConsoleOptionsFetcher)obj, false, false, false); } internal static void RegisterPeer(ZNet net, ZNetPeer peer) { try { if (net.IsServer()) { peer.m_rpc.Register<ZPackage>("YNW_AdminKeyRequest", (Action<ZRpc, ZPackage>)RPC_AdminRequest); peer.m_rpc.Register<ZPackage>("YNW_AdminKeyTargetResult", (Action<ZRpc, ZPackage>)RPC_TargetResult); } else { peer.m_rpc.Register<string>("YNW_AdminKeyResult", (Action<ZRpc, string>)RPC_AdminResult); peer.m_rpc.Register<ZPackage>("YNW_AdminKeyTargetRequest", (Action<ZRpc, ZPackage>)RPC_TargetRequest); } } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogError((object)$"Failed to register YNW admin-key RPCs: {arg}"); } } internal static void Update() { if (PendingRequests.Count == 0) { return; } if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { PendingRequests.Clear(); return; } float realtimeSinceStartup = Time.realtimeSinceStartup; List<long> list = null; foreach (KeyValuePair<long, PendingRequest> pendingRequest in PendingRequests) { if (!(realtimeSinceStartup < pendingRequest.Value.Deadline)) { if (list == null) { list = new List<long>(); } list.Add(pendingRequest.Key); } } if (list == null) { return; } foreach (long item in list) { if (PendingRequests.TryGetValue(item, out PendingRequest value)) { PendingRequests.Remove(item); SendAdminResult(value.Requester, "Timed out while applying " + DescribeOperation(value.Operation) + " for " + FormatTarget(value.PlayerName, value.SteamId) + "."); } } } internal static void Shutdown() { PendingRequests.Clear(); Dictionary<string, ConsoleCommand> terminalCommands = GetTerminalCommands(); if (_consoleCommand != null && terminalCommands != null && terminalCommands.TryGetValue("ynw:keys", out var value) && value == _consoleCommand) { terminalCommands.Remove("ynw:keys"); } _consoleCommand = null; } private static Dictionary<string, ConsoleCommand>? GetTerminalCommands() { try { return TerminalCommandsField?.GetValue(null) as Dictionary<string, ConsoleCommand>; } catch (Exception) { return null; } } private static void HandleConsoleCommand(ConsoleEventArgs args) { //IL_0023: 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) //IL_0089: Expected O, but got Unknown if ((Object)(object)ZNet.instance == (Object)null) { Terminal context = args.Context; if (context != null) { context.AddString("YNW player-key commands require an active server session."); } return; } if ((int)ZNet.m_onlineBackend != 0) { Terminal context2 = args.Context; if (context2 != null) { context2.AddString("YNW player-key commands support Steam networking only."); } return; } if (!ZNet.instance.IsServer() && !YouAreNotWorthyPlugin.IsLocalAdmin) { Terminal context3 = args.Context; if (context3 != null) { context3.AddString("You are not an admin on this server."); } return; } if (!TryParseCommand(args, out AdminOperation operation, out string steamId, out string key)) { PrintUsage(args.Context); return; } ZPackage val = new ZPackage(); val.Write((byte)operation); val.Write(steamId); val.Write(key); if (ZNet.instance.IsServer()) { val.SetPos(0); HandleAdminRequest(null, val); return; } ZNetPeer serverPeer = ZNet.instance.GetServerPeer(); if (serverPeer == null || !serverPeer.m_rpc.IsConnected()) { Terminal context4 = args.Context; if (context4 != null) { context4.AddString("The server connection is not ready."); } } else { serverPeer.m_rpc.Invoke("YNW_AdminKeyRequest", new object[1] { val }); } } private static bool TryParseCommand(ConsoleEventArgs args, out AdminOperation operation, out string steamId, out string key) { operation = (AdminOperation)0; steamId = string.Empty; key = string.Empty; string a = ((args.Length >= 2) ? (args[1] ?? string.Empty).Trim() : string.Empty); if (string.Equals(a, "players", StringComparison.OrdinalIgnoreCase)) { operation = AdminOperation.Players; return args.Length == 2; } if (string.Equals(a, "list", StringComparison.OrdinalIgnoreCase)) { operation = AdminOperation.List; if (args.Length == 3) { return TryNormalizeSteamId(args[2], out steamId); } return false; } if (string.Equals(a, "add", StringComparison.OrdinalIgnoreCase)) { operation = AdminOperation.Add; } else { if (!string.Equals(a, "remove", StringComparison.OrdinalIgnoreCase)) { return false; } operation = AdminOperation.Remove; } if (args.Length == 4 && TryNormalizeSteamId(args[2], out steamId)) { return TryNormalizeKey(args[3], out key); } return false; } private static void PrintUsage(Terminal? terminal) { if (terminal != null) { terminal.AddString("YNW online personal-key commands:"); } if (terminal != null) { terminal.AddString(" ynw:keys players"); } if (terminal != null) { terminal.AddString(" ynw:keys list <steamid64>"); } if (terminal != null) { terminal.AddString(" ynw:keys add <steamid64> <key>"); } if (terminal != null) { terminal.AddString(" ynw:keys remove <steamid64> <key>"); } } private static void RPC_AdminRequest(ZRpc requester, ZPackage request) { if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer()) { if (!IsRemoteAdmin(requester)) { YouAreNotWorthyPlugin.Log.LogWarning((object)("Rejected YNW player-key command from non-admin peer '" + GetPeerHostName(requester) + "'.")); SendAdminResult(requester, "You are not an admin on this server."); } else { HandleAdminRequest(requester, request); } } } private static void HandleAdminRequest(ZRpc? requester, ZPackage request) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) try { if ((int)ZNet.m_onlineBackend != 0) { SendAdminResult(requester, "YNW player-key commands support Steam networking only."); return; } AdminOperation adminOperation = (AdminOperation)request.ReadByte(); string value = request.ReadString(); string value2 = request.ReadString(); bool flag; switch (adminOperation) { case AdminOperation.Players: SendAdminResult(requester, BuildOnlinePlayersMessage()); return; case AdminOperation.List: case AdminOperation.Add: case AdminOperation.Remove: flag = true; break; default: flag = false; break; } if (!flag || !TryNormalizeSteamId(value, out string steamId)) { SendAdminResult(requester, "Invalid YNW player-key command request."); return; } string key = string.Empty; flag = adminOperation - 3 <= AdminOperation.Players; if (flag && !TryNormalizeKey(value2, out key)) { SendAdminResult(requester, "The personal key is invalid or is shared world state."); return; } List<OnlineTarget> list = GetOnlineTargets().FindAll((OnlineTarget target) => string.Equals(target.SteamId, steamId, StringComparison.Ordinal)); if (list.Count == 0) { SendAdminResult(requester, "Steam player " + steamId + " is not online with a loaded character."); return; } if (list.Count > 1) { SendAdminResult(requester, "Steam player " + steamId + " resolved to more than one connection; no key was changed."); return; } OnlineTarget onlineTarget = list[0]; LogAdminRequest(requester, adminOperation, onlineTarget, key); if (onlineTarget.IsLocal) { SendLocalTargetResult(requester, adminOperation, onlineTarget, key); } else { SendTargetRequest(requester, adminOperation, onlineTarget, key); } } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogError((object)$"Failed to handle a YNW player-key admin request: {arg}"); SendAdminResult(requester, "The YNW player-key request failed on the server. Check the server log."); } } private static void SendTargetRequest(ZRpc? requester, AdminOperation operation, OnlineTarget target, string key) { //IL_0085: Unknown result type (might be due to invalid IL or missing references) //IL_008b: Expected O, but got Unknown //IL_009b: Unknown result type (might be due to invalid IL or missing references) if (PendingRequests.Count >= 64 || CountPendingRequests(requester) >= 8) { SendAdminResult(requester, "Too many YNW player-key requests are awaiting target responses. Try again shortly."); return; } ZRpc rpc = target.Rpc; if (!rpc.IsConnected()) { SendAdminResult(requester, FormatTarget(target.PlayerName, target.SteamId) + " disconnected before the request was sent."); return; } long num = NextRequestToken(); PendingRequests[num] = new PendingRequest(requester, rpc, operation, target.SteamId, target.PlayerName, key, Time.realtimeSinceStartup + 10f); ZPackage val = new ZPackage(); val.Write(num); val.Write((byte)operation); val.Write(target.CharacterId); val.Write(key); try { rpc.Invoke("YNW_AdminKeyTargetRequest", new object[1] { val }); } catch (Exception arg) { PendingRequests.Remove(num); YouAreNotWorthyPlugin.Log.LogWarning((object)$"Failed to send YNW player-key request to {FormatTarget(target.PlayerName, target.SteamId)}: {arg}"); SendAdminResult(requester, "Could not contact " + FormatTarget(target.PlayerName, target.SteamId) + "."); } } private static void RPC_TargetRequest(ZRpc server, ZPackage request) { //IL_005d: Unknown result type (might be due to invalid IL or missing references) //IL_0063: Expected O, but got Unknown //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: 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) if (!IsServerConnection(server)) { YouAreNotWorthyPlugin.Log.LogWarning((object)"Ignored a YNW player-key target request from a non-server connection."); return; } long num = 0L; ZPackage val; try { num = request.ReadLong(); AdminOperation operation = (AdminOperation)request.ReadByte(); ZDOID expectedCharacter = request.ReadZDOID(); string requestedKey = request.ReadString(); val = BuildTargetResponse(num, operation, expectedCharacter, requestedKey); } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogError((object)$"Failed to apply a YNW player-key target request: {arg}"); val = new ZPackage(); val.Write(num); val.Write((byte)9); } server.Invoke("YNW_AdminKeyTargetResult", new object[1] { val }); } private static ZPackage BuildTargetResponse(long token, AdminOperation operation, ZDOID expectedCharacter, string requestedKey) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0006: Expected O, but got Unknown //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) ZPackage val = new ZPackage(); val.Write(token); Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer == (Object)null) { val.Write((byte)6); } else if (((Character)localPlayer).GetZDOID() != expectedCharacter) { val.Write((byte)7); } else if (operation == AdminOperation.List) { WriteKeyListResponse(val, localPlayer); } else if (operation - 3 <= AdminOperation.Players) { WriteMutationResponse(val, requestedKey, operation == AdminOperation.Add); } else { val.Write((byte)9); } return val; } private static void WriteMutationResponse(ZPackage response, string requestedKey, bool add) { if (!TryNormalizeKey(requestedKey, out string key)) { response.Write((byte)5); return; } TargetResult targetResult = PlayerKeys.MutateLocal(key, add) switch { PersonalKeyMutationResult.Added => TargetResult.Added, PersonalKeyMutationResult.AlreadyPresent => TargetResult.AlreadyPresent, PersonalKeyMutationResult.Removed => TargetResult.Removed, PersonalKeyMutationResult.NotPresent => TargetResult.NotPresent, PersonalKeyMutationResult.InvalidKey => TargetResult.InvalidKey, PersonalKeyMutationResult.NoLocalPlayer => TargetResult.NoLocalPlayer, _ => TargetResult.Failed, }; response.Write((byte)targetResult); if ((targetResult == TargetResult.Added || targetResult == TargetResult.Removed) ? true : false) { response.Write(TrySaveLocalProfile()); } } private static void WriteKeyListResponse(ZPackage response, Player player) { List<string> list = new List<string>(player.GetUniqueKeys()); list.RemoveAll((string key) => string.IsNullOrWhiteSpace(key)); list.Sort(StringComparer.OrdinalIgnoreCase); int num = Math.Min(list.Count, 512); response.Write((byte)8); response.Write(list.Count); response.Write(num); for (int num2 = 0; num2 < num; num2++) { response.Write(SanitizeForConsole(list[num2], 256)); } } private static void RPC_TargetResult(ZRpc target, ZPackage response) { if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer()) { return; } long key; try { key = response.ReadLong(); } catch (Exception ex) { YouAreNotWorthyPlugin.Log.LogWarning((object)("Ignored a malformed YNW player-key result: " + ex.Message)); return; } if (!PendingRequests.TryGetValue(key, out PendingRequest value)) { YouAreNotWorthyPlugin.Log.LogWarning((object)"Ignored an unknown or expired YNW player-key result."); return; } if (value.Target != target) { YouAreNotWorthyPlugin.Log.LogWarning((object)"Ignored a YNW player-key result from the wrong peer."); return; } PendingRequests.Remove(key); try { string message = ReadTargetResultMessage(value.Operation, value.SteamId, value.PlayerName, value.Key, response); SendAdminResult(value.Requester, message); } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogWarning((object)$"Failed to read a YNW player-key result: {arg}"); SendAdminResult(value.Requester, "The target returned an invalid YNW player-key result."); } } private static string ReadTargetResultMessage(AdminOperation operation, string steamId, string playerName, string key, ZPackage response) { TargetResult targetResult = (TargetResult)response.ReadByte(); if (!IsCompatibleTargetResult(operation, targetResult)) { throw new InvalidOperationException($"Target result '{targetResult}' is not valid for operation '{operation}'."); } return BuildTargetResultMessage(steamId, playerName, key, targetResult, response); } private static string BuildTargetResultMessage(string steamId, string playerName, string key, TargetResult result, ZPackage response) { string text = FormatTarget(playerName, steamId); return result switch { TargetResult.Added => AppendSaveWarning("Added personal key '" + key + "' to " + text + ".", response.ReadBool()), TargetResult.AlreadyPresent => text + " already has personal key '" + key + "'.", TargetResult.Removed => AppendSaveWarning("Removed personal key '" + key + "' from " + text + ".", response.ReadBool()), TargetResult.NotPresent => text + " does not have personal key '" + key + "'.", TargetResult.InvalidKey => "Personal key '" + key + "' was rejected by " + text + ".", TargetResult.NoLocalPlayer => text + " no longer has a loaded local character.", TargetResult.CharacterChanged => text + " changed character before the key operation completed.", TargetResult.Listed => ReadKeyListMessage(playerName, steamId, response), _ => "The personal-key operation failed for " + text + ".", }; } private static bool IsCompatibleTargetResult(AdminOperation operation, TargetResult result) { if ((result - 6 <= TargetResult.Added || result == TargetResult.Failed) ? true : false) { return true; } return operation switch { AdminOperation.List => result == TargetResult.Listed, AdminOperation.Add => (result - 1 <= TargetResult.Added || result == TargetResult.InvalidKey) ? true : false, AdminOperation.Remove => result - 3 <= TargetResult.AlreadyPresent, _ => false, }; } private static string ReadKeyListMessage(string playerName, string steamId, ZPackage response) { int num = response.ReadInt(); int num2 = response.ReadInt(); if (num < 0 || num2 < 0 || num2 > num || num2 > 512) { throw new InvalidOperationException("The target returned an invalid personal-key count."); } StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("Personal keys for ").Append(FormatTarget(playerName, steamId)); stringBuilder.Append(" (").Append(num.ToString(CultureInfo.InvariantCulture)).AppendLine("):"); int num3 = 0; for (int i = 0; i < num2; i++) { string text = SanitizeForConsole(response.ReadString(), 256); if (stringBuilder.Length + text.Length + 4 <= 16384) { stringBuilder.Append(" ").AppendLine(text); num3++; } } if (num3 < num) { stringBuilder.Append(" ... ").Append((num - num3).ToString(CultureInfo.InvariantCulture)).Append(" more key(s) omitted"); } return stringBuilder.ToString().TrimEnd(Array.Empty<char>()); } private static void SendLocalTargetResult(ZRpc? requester, AdminOperation operation, OnlineTarget target, string key) { //IL_0004: Unknown result type (might be due to invalid IL or missing references) ZPackage val = BuildTargetResponse(0L, operation, target.CharacterId, key); val.SetPos(0); try { val.ReadLong(); string message = ReadTargetResultMessage(operation, target.SteamId, target.PlayerName, key, val); SendAdminResult(requester, message); } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogWarning((object)$"Failed to read a YNW player-key result: {arg}"); SendAdminResult(requester, "The target returned an invalid YNW player-key result."); } } private static string BuildOnlinePlayersMessage() { List<OnlineTarget> onlineTargets = GetOnlineTargets(); onlineTargets.Sort((OnlineTarget left, OnlineTarget right) => string.Compare(left.SteamId, right.SteamId, StringComparison.Ordinal)); if (onlineTargets.Count == 0) { return "No Steam players with loaded characters are online."; } StringBuilder stringBuilder = new StringBuilder("Online YNW player-key targets:"); foreach (OnlineTarget item in onlineTargets) { stringBuilder.AppendLine().Append(" ").Append(SanitizeForConsole(item.PlayerName, 64)) .Append(" | ") .Append(item.SteamId); } return stringBuilder.ToString(); } private static List<OnlineTarget> GetOnlineTargets() { //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0051: Unknown result type (might be due to invalid IL or missing references) //IL_00c5: Unknown result type (might be due to invalid IL or missing references) List<OnlineTarget> list = new List<OnlineTarget>(); ZNet instance = ZNet.instance; if ((Object)(object)instance == (Object)null || !instance.IsServer()) { return list; } Player localPlayer = Player.m_localPlayer; if ((Object)(object)localPlayer != (Object)null && TryGetLocalSteamId(out string steamId)) { ZDOID zDOID = ((Character)localPlayer).GetZDOID(); if (!((ZDOID)(ref zDOID)).IsNone()) { list.Add(new OnlineTarget(steamId, localPlayer.GetPlayerName(), ((Character)localPlayer).GetZDOID(), null)); } } foreach (ZNetPeer connectedPeer in instance.GetConnectedPeers()) { if (connectedPeer.IsReady() && !((ZDOID)(ref connectedPeer.m_characterID)).IsNone() && TryNormalizeConnectedSteamId(connectedPeer.m_socket.GetHostName(), out string steamId2) && !((Object)(object)PlayerKeys.FindPlayerByOwner(connectedPeer.m_uid) == (Object)null)) { list.Add(new OnlineTarget(steamId2, connectedPeer.m_playerName, connectedPeer.m_characterID, connectedPeer.m_rpc)); } } return list; } private static bool TryGetLocalSteamId(out string steamId) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_0013: Unknown result type (might be due to invalid IL or missing references) steamId = string.Empty; try { return SteamUser.BLoggedOn() && TryNormalizeSteamId(((object)SteamUser.GetSteamID()/*cast due to .constrained prefix*/).ToString(), out steamId); } catch (Exception) { return false; } } private static bool TryNormalizeConnectedSteamId(string? value, out string steamId) { string text = (value ?? string.Empty).Trim(); if (text.StartsWith("Steam_", StringComparison.OrdinalIgnoreCase)) { text = text.Substring("Steam_".Length); } return TryNormalizeSteamId(text, out steamId); } private static bool TryNormalizeSteamId(string? value, out string steamId) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) string text = (value ?? string.Empty).Trim(); if (text.Length == 17 && ulong.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out var result) && result != 0L) { CSteamID val = new CSteamID(result); if (((CSteamID)(ref val)).IsValid()) { steamId = result.ToString(CultureInfo.InvariantCulture); return true; } } steamId = string.Empty; return false; } private static bool TryNormalizeKey(string? value, out string key) { string text = (value ?? string.Empty).Trim(); if (text.Length == 0 || text.Length > 256) { key = string.Empty; return false; } string text2 = text; foreach (char c in text2) { if (char.IsControl(c) || char.IsWhiteSpace(c)) { key = string.Empty; return false; } } return ProgressionIndex.TryResolvePersonalKey(text, out key); } private static bool TrySaveLocalProfile() { try { if ((Object)(object)Game.instance == (Object)null) { return false; } Game.instance.SavePlayerProfile(false); return true; } catch (Exception arg) { YouAreNotWorthyPlugin.Log.LogError((object)$"Failed to save the local character after an admin key change: {arg}"); return false; } } private static bool IsRemoteAdmin(ZRpc requester) { try { ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? instance.GetPeer(requester) : null); return (Object)(object)instance != (Object)null && instance.IsServer() && val != null && val.IsReady() && val.m_rpc == requester && instance.IsAdmin(requester.GetSocket().GetHostName()); } catch (Exception) { return false; } } private static bool IsServerConnection(ZRpc rpc) { try { ZNet instance = ZNet.instance; ZNetPeer val = ((instance != null) ? instance.GetServerPeer() : null); return val != null && val.m_server && val.m_rpc == rpc; } catch (Exception) { return false; } } private static void RPC_AdminResult(ZRpc server, string message) { if (!IsServerConnection(server)) { YouAreNotWorthyPlugin.Log.LogWarning((object)"Ignored a YNW admin-key result from a non-server connection."); } else { PrintConsole(message); } } private static void SendAdminResult(ZRpc? requester, string message) { string text = SanitizeMultilineForConsole(message, 16384); YouAreNotWorthyPlugin.Log.LogInfo((object)text); if (requester == null) { PrintConsole(text); return; } try { requester.Invoke("YNW_AdminKeyResult", new object[1] { text }); } catch (Exception ex) { YouAreNotWorthyPlugin.Log.LogWarning((object)("Failed to return a YNW admin-key result: " + ex.Message)); } } private static void PrintConsole(string message) { if ((Object)(object)Console.instance != (Object)null) { ((Terminal)Console.instance).AddString(message); } } private static void LogAdminRequest(ZRpc? requester, AdminOperation operation, OnlineTarget target, string key) { string text = ((requester == null) ? "local-server" : GetPeerHostName(requester)); string text2 = ((key.Length == 0) ? string.Empty : (", key='" + key + "'")); YouAreNotWorthyPlugin.Log.LogInfo((object)("Admin '" + text + "' requested " + DescribeOperation(operation) + " for " + FormatTarget(target.PlayerName, target.SteamId) + text2 + ".")); } private static string GetPeerHostName(ZRpc rpc) { try { return SanitizeForConsole(rpc.GetSocket().GetHostName(), 128); } catch (Exception) { return "unknown"; } } private static string FormatTarget(string playerName, string steamId) { return SanitizeForCon