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 VeiledRecipes v1.0.4
VeiledRecipes.dll
Decompiled 2 weeks ago
The result has been truncated due to the large size, download it to view full contents!
using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Compression; using System.Linq; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Serialization; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using JetBrains.Annotations; using Microsoft.CodeAnalysis; using ServerSync; using TMPro; using UnityEngine; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("VeiledRecipes")] [assembly: AssemblyDescription("")] [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("sighsorry")] [assembly: AssemblyProduct("VeiledRecipes")] [assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: ComVisible(false)] [assembly: Guid("4358610B-F3F4-4843-B7AF-98B7BC60DCDE")] [assembly: AssemblyFileVersion("1.0.4")] [assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.0.4.0")] [module: UnverifiableCode] [module: RefSafetyRules(11)] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)] internal sealed class NullableContextAttribute : Attribute { public readonly byte Flag; public NullableContextAttribute(byte P_0) { Flag = P_0; } } [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] [AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)] internal sealed class RefSafetyRulesAttribute : Attribute { public readonly int Version; public RefSafetyRulesAttribute(int P_0) { Version = P_0; } } } namespace VeiledRecipes { [BepInPlugin("sighsorry.VeiledRecipes", "VeiledRecipes", "1.0.4")] public class VeiledRecipesPlugin : BaseUnityPlugin { public enum Toggle { On = 1, Off = 0 } internal const string ModName = "VeiledRecipes"; internal const string ModVersion = "1.0.4"; internal const string Author = "sighsorry"; public const string ModGUID = "sighsorry.VeiledRecipes"; private static string ConfigFileName = "sighsorry.VeiledRecipes.cfg"; private static string ConfigFileFullPath; private readonly Harmony _harmony = new Harmony("sighsorry.VeiledRecipes"); public static readonly ManualLogSource PluginLogger; private static readonly ConfigSync ConfigSync; private FileSystemWatcher? _watcher; private readonly object _reloadLock = new object(); private DateTime _lastConfigReloadTime; private static readonly TimeSpan ConfigReloadDebounce; private static ConfigEntry<Toggle> _serverConfigLocked; internal static ConfigEntry<Toggle> ShowUnknownCraftingRecipes; internal static ConfigEntry<Toggle> ShowUnknownBuildPieces; internal static ConfigEntry<Toggle> RequireStationLevelForUnknownCraftingRecipes; internal static ConfigEntry<Toggle> RequireStationKnowledgeForUnknownBuildPieces; internal static ConfigEntry<Toggle> RequireStationInteractionForRecipeUnlock; internal static ConfigEntry<Toggle> EnableStationProximityDiscovery; internal static ConfigEntry<string> RecipePreviewPrefabBlacklist; internal static ConfigEntry<string> PiecePreviewPrefabBlacklist; internal static ConfigEntry<string> RequirementPreviewPrefabBlacklist; internal static ConfigEntry<string> UnknownNameText; internal static ConfigEntry<string> UnknownDescriptionText; internal static ConfigEntry<string> UnknownRequirementText; internal static ConfigEntry<bool> GroupUnknownRecipePreviewsBelowKnownRecipes; internal static ConfigEntry<bool> ShowRecipeUnlockNotifications; internal static ConfigEntry<bool> ShowPieceUnlockNotifications; internal static ConfigEntry<bool> ShowSkillLevelUpNotificationAndEffect; public void Awake() { bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; _serverConfigLocked = config("1 - General", "Lock Configuration", Toggle.On, "If on, the configuration is locked and can be changed by server admins only."); ConfigSync.AddLockingConfigEntry<Toggle>(_serverConfigLocked); ShowUnknownCraftingRecipes = config("2 - Veiled Recipes", "Show Unknown Crafting Recipes", Toggle.On, "Shows crafting recipes at the relevant crafting station before the recipe is fully unlocked."); ShowUnknownBuildPieces = config("2 - Veiled Recipes", "Show Unknown Build Pieces", Toggle.On, "Shows build pieces in build-piece tables before the piece is fully unlocked."); RequireStationLevelForUnknownCraftingRecipes = config("2 - Veiled Recipes", "Require Station Level For Unknown Crafting Recipes", Toggle.On, "If on, unknown crafting recipe previews are shown only when the current crafting station meets the recipe's required station level."); RequireStationKnowledgeForUnknownBuildPieces = config("2 - Veiled Recipes", "Require Station Knowledge For Unknown Build Pieces", Toggle.On, "If on, unknown build-piece previews that require a crafting station are shown only after the player knows that station."); RequireStationInteractionForRecipeUnlock = config("2 - Veiled Recipes", "Require Station Interaction For Recipe Unlock", Toggle.On, "If on, recipes that require a crafting station unlock only after the player has interacted with the required station level. If off, Valheim's normal station discovery behavior is used for recipe station knowledge."); EnableStationProximityDiscovery = config("2 - Veiled Recipes", "Enable Station Proximity Discovery", Toggle.On, "If on, Valheim's normal crafting station discovery radius is used. If off, walking near a crafting station does not discover it; interacting with the station is required."); RecipePreviewPrefabBlacklist = config("2 - Veiled Recipes", "Recipe Preview Prefab Blacklist", "", "Comma-separated item prefab names whose unknown crafting recipe previews should never be shown. This does not hide recipes after they are actually unlocked. Example: ArmorIronLegs, SwordIron"); PiecePreviewPrefabBlacklist = config("2 - Veiled Recipes", "Piece Preview Prefab Blacklist", "", "Comma-separated piece prefab names whose unknown build piece previews should never be shown. This does not hide pieces after they are actually unlocked. Example: piece_workbench_ext1, piece_chest"); RequirementPreviewPrefabBlacklist = config("2 - Veiled Recipes", "Requirement Preview Prefab Blacklist", "SwordCheat, SledgeCheat", "Comma-separated ingredient/resource prefab names that prevent unknown crafting recipe and build-piece previews from being shown when required by that recipe or piece. This does not hide anything after it is actually unlocked."); UnknownNameText = config("3 - Display", "Unknown Name Text", "???", "Text shown for unknown recipe and piece names."); UnknownDescriptionText = config("3 - Display", "Unknown Description Text", "Not enough info", "Text shown for unknown recipe and piece descriptions."); UnknownRequirementText = config("3 - Display", "Unknown Requirement Text", "?", "Text shown for unknown requirement names, amounts, and station levels."); GroupUnknownRecipePreviewsBelowKnownRecipes = config("3 - Display", "Group Unknown Recipe Previews Below Known Recipes", value: true, "If enabled, unknown crafting recipe previews are grouped below actually unlocked recipes in crafting station recipe lists.", synchronizedSetting: false); ShowRecipeUnlockNotifications = config("4 - Client Notifications", "Show Recipe Unlock Notifications", value: true, "Shows Valheim's unlock popup when a crafting recipe is learned.", synchronizedSetting: false); ShowPieceUnlockNotifications = config("4 - Client Notifications", "Show Piece Unlock Notifications", value: false, "Shows Valheim's unlock popup when a build piece or dish is learned.", synchronizedSetting: false); ShowSkillLevelUpNotificationAndEffect = config("4 - Client Notifications", "Show Skill Level Up Notification/Effect", value: true, "Shows the skill level-up message and VFX/SFX. When disabled, all $msg_skillup alarms and Player.OnSkillLevelup effects are hidden.", synchronizedSetting: false); VeiledRecipeInfinityHammerCompat.RegisterKnownPieceOverrides(); Assembly executingAssembly = Assembly.GetExecutingAssembly(); _harmony.PatchAll(executingAssembly); SetupWatcher(); ((BaseUnityPlugin)this).Config.Save(); if (saveOnConfigSet) { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } } private void OnDestroy() { SaveWithRespectToConfigSet(); _watcher?.Dispose(); } private void SetupWatcher() { _watcher = new FileSystemWatcher(Paths.ConfigPath, ConfigFileName); _watcher.Changed += ReadConfigValues; _watcher.Created += ReadConfigValues; _watcher.Renamed += ReadConfigValues; _watcher.IncludeSubdirectories = true; _watcher.SynchronizingObject = ThreadingHelper.SynchronizingObject; _watcher.EnableRaisingEvents = true; } private void ReadConfigValues(object sender, FileSystemEventArgs e) { DateTime now = DateTime.Now; if (now - _lastConfigReloadTime < ConfigReloadDebounce) { return; } lock (_reloadLock) { if (!File.Exists(ConfigFileFullPath)) { PluginLogger.LogWarning((object)"Config file does not exist. Skipping reload."); return; } try { PluginLogger.LogDebug((object)"Reloading configuration..."); SaveWithRespectToConfigSet(reload: true); PluginLogger.LogInfo((object)"Configuration reload complete."); } catch (Exception ex) { PluginLogger.LogError((object)("Error reloading configuration: " + ex.Message)); } } _lastConfigReloadTime = now; } private void SaveWithRespectToConfigSet(bool reload = false) { bool saveOnConfigSet = ((BaseUnityPlugin)this).Config.SaveOnConfigSet; ((BaseUnityPlugin)this).Config.SaveOnConfigSet = false; if (reload) { ((BaseUnityPlugin)this).Config.Reload(); } ((BaseUnityPlugin)this).Config.Save(); if (saveOnConfigSet) { ((BaseUnityPlugin)this).Config.SaveOnConfigSet = saveOnConfigSet; } } private ConfigEntry<T> config<T>(string group, string name, T value, ConfigDescription description, bool synchronizedSetting = true) { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown ConfigDescription val = new ConfigDescription(description.Description + (synchronizedSetting ? " [Synced with Server]" : " [Not Synced with Server]"), description.AcceptableValues, description.Tags); ConfigEntry<T> val2 = ((BaseUnityPlugin)this).Config.Bind<T>(group, name, value, val); ConfigSync.AddConfigEntry<T>(val2).SynchronizedConfig = synchronizedSetting; return val2; } private ConfigEntry<T> config<T>(string group, string name, T value, string description, bool synchronizedSetting = true) { //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Expected O, but got Unknown return config(group, name, value, new ConfigDescription(description, (AcceptableValueBase)null, Array.Empty<object>()), synchronizedSetting); } static VeiledRecipesPlugin() { string configPath = Paths.ConfigPath; char directorySeparatorChar = Path.DirectorySeparatorChar; ConfigFileFullPath = configPath + directorySeparatorChar + ConfigFileName; PluginLogger = Logger.CreateLogSource("VeiledRecipes"); ConfigSync = new ConfigSync("sighsorry.VeiledRecipes") { DisplayName = "VeiledRecipes", CurrentVersion = "1.0.4", MinimumRequiredVersion = "1.0.4" }; ConfigReloadDebounce = TimeSpan.FromSeconds(1.0); _serverConfigLocked = null; ShowUnknownCraftingRecipes = null; ShowUnknownBuildPieces = null; RequireStationLevelForUnknownCraftingRecipes = null; RequireStationKnowledgeForUnknownBuildPieces = null; RequireStationInteractionForRecipeUnlock = null; EnableStationProximityDiscovery = null; RecipePreviewPrefabBlacklist = null; PiecePreviewPrefabBlacklist = null; RequirementPreviewPrefabBlacklist = null; UnknownNameText = null; UnknownDescriptionText = null; UnknownRequirementText = null; GroupUnknownRecipePreviewsBelowKnownRecipes = null; ShowRecipeUnlockNotifications = null; ShowPieceUnlockNotifications = null; ShowSkillLevelUpNotificationAndEffect = null; } } internal static class VeiledRecipeConstants { internal const string StationInteractionPrefix = "VeiledRecipes.InteractedStation."; internal const string UnknownNameFallback = "???"; internal const string UnknownDescriptionFallback = "Not enough info"; internal const string UnknownRequirementFallback = "?"; internal const string NewRecipeMessage = "$msg_newrecipe"; internal const string NewPieceMessage = "$msg_newpiece"; internal const string NewDishMessage = "$msg_newdish"; internal const string SkillUpMessagePrefix = "$msg_skillup "; internal const string MissingRequirementMessage = "$msg_missingrequirement"; internal const string MenuNoneMessage = "$menu_none"; internal const string CloneSuffix = "(Clone)"; internal const string RecipeIconChild = "icon"; internal const string RecipeNameChild = "name"; internal const string DurabilityChild = "Durability"; internal const string QualityLevelChild = "QualityLevel"; internal const string RequirementIconChild = "res_icon"; internal const string RequirementNameChild = "res_name"; internal const string RequirementAmountChild = "res_amount"; internal static readonly char[] PrefabBlacklistSeparators = new char[5] { ',', ';', '|', '\n', '\r' }; } internal static class VeiledRecipeInfinityHammerCompat { private const string PluginGuid = "infinity_hammer"; private const string BuildMenuToolTypeName = "InfinityHammer.BuildMenuTool"; private const string SelectionTypeName = "InfinityHammer.Selection"; private const string BaseSelectionTypeName = "InfinityHammer.BaseSelection"; private static bool _initialized; private static bool _loaded; private static MethodInfo? _getSelectionMethod; private static MethodInfo? _getSelectedPieceMethod; private static PropertyInfo? _isToolProperty; internal static void RegisterKnownPieceOverrides() { VeiledRecipeState.RegisterKnownPieceTypeOverride("InfinityHammer.BuildMenuTool"); VeiledRecipeState.RegisterKnownPieceOverride(IsActiveToolSelectionPiece); } internal static bool IsActiveToolSelectionPiece(Piece piece) { if ((Object)(object)piece == (Object)null) { return false; } EnsureInitialized(); if (!_loaded || _getSelectionMethod == null || _getSelectedPieceMethod == null || _isToolProperty == null) { return false; } try { object obj = _getSelectionMethod.Invoke(null, null); if (!IsToolSelection(obj)) { return false; } Piece val = (Piece)((obj == null) ? null : /*isinst with value type is only supported in some contexts*/); return (Object)(object)val != (Object)null && val == piece; } catch (Exception ex) { VeiledRecipesPlugin.PluginLogger.LogDebug((object)("Infinity Hammer active tool selection check failed: " + ex.Message)); return false; } } private static bool IsToolSelection(object? selection) { if (selection != null) { object obj = _isToolProperty?.GetValue(selection); if (obj is bool) { return (bool)obj; } return false; } return false; } private static void EnsureInitialized() { if (!_initialized) { _initialized = true; if (Chainloader.PluginInfos.TryGetValue("infinity_hammer", out var value)) { Assembly obj = ((object)value.Instance)?.GetType().Assembly; Type type = obj?.GetType("InfinityHammer.Selection", throwOnError: false); Type? obj2 = obj?.GetType("InfinityHammer.BaseSelection", throwOnError: false); _getSelectionMethod = type?.GetMethod("Get", BindingFlags.Static | BindingFlags.Public); _getSelectedPieceMethod = obj2?.GetMethod("GetSelectedPiece", BindingFlags.Instance | BindingFlags.Public); _isToolProperty = obj2?.GetProperty("IsTool", BindingFlags.Instance | BindingFlags.Public); _loaded = _getSelectionMethod != null && _getSelectedPieceMethod != null && _isToolProperty != null; } } } } internal static class VeiledRecipeState { private static readonly List<Func<Piece, bool>> KnownPieceOverrides = new List<Func<Piece, bool>>(); private static readonly HashSet<string> KnownPiecePrefabOverrides = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static readonly HashSet<string> KnownPieceTypeOverrides = new HashSet<string>(StringComparer.Ordinal); private static string _recipePreviewPrefabBlacklistRaw = ""; private static string _piecePreviewPrefabBlacklistRaw = ""; private static string _requirementPreviewPrefabBlacklistRaw = ""; private static HashSet<string> _recipePreviewPrefabBlacklist = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static HashSet<string> _piecePreviewPrefabBlacklist = new HashSet<string>(StringComparer.OrdinalIgnoreCase); private static HashSet<string> _requirementPreviewPrefabBlacklist = new HashSet<string>(StringComparer.OrdinalIgnoreCase); internal static bool ShowUnknownCraftingRecipes => IsOn(VeiledRecipesPlugin.ShowUnknownCraftingRecipes); internal static bool ShowUnknownBuildPieces => IsOn(VeiledRecipesPlugin.ShowUnknownBuildPieces); internal static bool RequireStationLevelForUnknownCraftingRecipes => IsOn(VeiledRecipesPlugin.RequireStationLevelForUnknownCraftingRecipes); internal static bool RequireStationKnowledgeForUnknownBuildPieces => IsOn(VeiledRecipesPlugin.RequireStationKnowledgeForUnknownBuildPieces); internal static bool RequireStationInteractionForRecipeUnlock => IsOn(VeiledRecipesPlugin.RequireStationInteractionForRecipeUnlock); internal static bool EnableStationProximityDiscovery => IsOn(VeiledRecipesPlugin.EnableStationProximityDiscovery); internal static string UnknownNameText => SafeText(VeiledRecipesPlugin.UnknownNameText, "???"); internal static string UnknownDescriptionText => SafeText(VeiledRecipesPlugin.UnknownDescriptionText, "Not enough info"); internal static string UnknownRequirementText => SafeText(VeiledRecipesPlugin.UnknownRequirementText, "?"); internal static bool GroupUnknownRecipePreviewsBelowKnownRecipes => VeiledRecipesPlugin.GroupUnknownRecipePreviewsBelowKnownRecipes?.Value ?? true; internal static bool ShowRecipeUnlockNotifications => VeiledRecipesPlugin.ShowRecipeUnlockNotifications?.Value ?? true; internal static bool ShowPieceUnlockNotifications => VeiledRecipesPlugin.ShowPieceUnlockNotifications?.Value ?? true; internal static bool ShowSkillLevelUpNotificationAndEffect => VeiledRecipesPlugin.ShowSkillLevelUpNotificationAndEffect?.Value ?? true; internal static bool IsRecipeActuallyKnown(Player player, Recipe recipe) { if ((Object)(object)player == (Object)null || (Object)(object)recipe == (Object)null || (Object)(object)recipe.m_item == (Object)null) { return false; } if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)23)) { return true; } if (player.m_noPlacementCost || player.NoCostCheat()) { return true; } return player.m_knownRecipes.Contains(recipe.m_item.m_itemData.m_shared.m_name); } internal static bool IsPieceActuallyKnown(Player player, Piece piece) { if ((Object)(object)player == (Object)null || (Object)(object)piece == (Object)null) { return false; } if (piece.m_repairPiece || piece.m_removePiece) { return true; } if ((Object)(object)ZoneSystem.instance != (Object)null && ZoneSystem.instance.GetGlobalKey((GlobalKeys)21)) { return true; } if (player.m_noPlacementCost || player.NoCostCheat()) { return true; } if (HasKnownPieceOverride(piece)) { return true; } return player.m_knownRecipes.Contains(piece.m_name); } internal static bool IsMaterialKnown(Player player, Requirement requirement) { if ((Object)(object)player == (Object)null || requirement == null || (Object)(object)requirement.m_resItem == (Object)null) { return false; } return player.m_knownMaterial.Contains(requirement.m_resItem.m_itemData.m_shared.m_name); } private static bool DlcInstalled(string dlc) { if (!string.IsNullOrEmpty(dlc) && !((Object)(object)DLCMan.instance == (Object)null)) { return DLCMan.instance.IsDLCInstalled(dlc); } return true; } internal static void RegisterKnownPieceOverride(Func<Piece, bool> predicate) { if (predicate != null && !KnownPieceOverrides.Contains(predicate)) { KnownPieceOverrides.Add(predicate); } } internal static void UnregisterKnownPieceOverride(Func<Piece, bool> predicate) { if (predicate != null) { KnownPieceOverrides.Remove(predicate); } } internal static void RegisterKnownPiecePrefabOverride(string prefabName) { if (!string.IsNullOrWhiteSpace(prefabName)) { KnownPiecePrefabOverrides.Add(prefabName.Trim()); } } internal static void UnregisterKnownPiecePrefabOverride(string prefabName) { if (!string.IsNullOrWhiteSpace(prefabName)) { KnownPiecePrefabOverrides.Remove(prefabName.Trim()); } } internal static void RegisterKnownPieceTypeOverride(string typeName) { if (!string.IsNullOrWhiteSpace(typeName)) { KnownPieceTypeOverrides.Add(typeName.Trim()); } } internal static void UnregisterKnownPieceTypeOverride(string typeName) { if (!string.IsNullOrWhiteSpace(typeName)) { KnownPieceTypeOverrides.Remove(typeName.Trim()); } } private static bool HasKnownPieceOverride(Piece piece) { if ((Object)(object)piece == (Object)null) { return false; } string prefabName = Utils.GetPrefabName(((Component)piece).gameObject); if (!string.IsNullOrEmpty(prefabName) && KnownPiecePrefabOverrides.Contains(prefabName)) { return true; } string fullName = ((object)piece).GetType().FullName; if (!string.IsNullOrEmpty(fullName) && KnownPieceTypeOverrides.Contains(fullName)) { return true; } foreach (Func<Piece, bool> knownPieceOverride in KnownPieceOverrides) { try { if (knownPieceOverride(piece)) { return true; } } catch (Exception ex) { VeiledRecipesPlugin.PluginLogger.LogDebug((object)("Known piece override failed for '" + prefabName + "': " + ex.Message)); } } return false; } internal static bool ShouldShowUnlockNotification(string topic) { switch (NormalizeToken(topic)) { case "$msg_newrecipe": return ShowRecipeUnlockNotifications; case "$msg_newpiece": case "$msg_newdish": return ShowPieceUnlockNotifications; default: return true; } } internal static bool ShouldShowSkillLevelUpNotification(string message) { if (IsSkillNotificationMessage(message)) { return ShowSkillLevelUpNotificationAndEffect; } return true; } internal static bool ShouldShowSkillLevelUpEffect() { return ShowSkillLevelUpNotificationAndEffect; } private static bool IsSkillNotificationMessage(string message) { return NormalizeToken(message).StartsWith("$msg_skillup ", StringComparison.OrdinalIgnoreCase); } private static string NormalizeToken(string value) { return (value ?? "").Trim(); } private static bool IsOn(ConfigEntry<VeiledRecipesPlugin.Toggle> entry) { if (entry != null) { return entry.Value == VeiledRecipesPlugin.Toggle.On; } return false; } private static string SafeText(ConfigEntry<string> entry, string fallback) { if (entry == null || string.IsNullOrEmpty(entry.Value)) { return fallback; } return entry.Value; } private static bool IsRecipePreviewBlacklisted(Recipe recipe) { HashSet<string> recipePreviewPrefabBlacklist = GetRecipePreviewPrefabBlacklist(); if (recipePreviewPrefabBlacklist.Count > 0) { string[] obj = new string[4] { ((Object)recipe).name, null, null, null }; ItemDrop item = recipe.m_item; obj[1] = ((item != null) ? ((Object)item).name : null); ItemDrop item2 = recipe.m_item; object obj2; if (item2 == null) { obj2 = null; } else { GameObject gameObject = ((Component)item2).gameObject; obj2 = ((gameObject != null) ? ((Object)gameObject).name : null); } obj[2] = (string)obj2; ItemDrop item3 = recipe.m_item; object obj3; if (item3 == null) { obj3 = null; } else { GameObject dropPrefab = item3.m_itemData.m_dropPrefab; obj3 = ((dropPrefab != null) ? ((Object)dropPrefab).name : null); } obj[3] = (string)obj3; return ContainsPrefabName(recipePreviewPrefabBlacklist, obj); } return false; } private static bool IsPiecePreviewBlacklisted(Piece piece) { HashSet<string> piecePreviewPrefabBlacklist = GetPiecePreviewPrefabBlacklist(); if (piecePreviewPrefabBlacklist.Count > 0) { string[] obj = new string[2] { ((Object)piece).name, null }; GameObject gameObject = ((Component)piece).gameObject; obj[1] = ((gameObject != null) ? ((Object)gameObject).name : null); return ContainsPrefabName(piecePreviewPrefabBlacklist, obj); } return false; } private static HashSet<string> GetRecipePreviewPrefabBlacklist() { return GetPrefabBlacklist(VeiledRecipesPlugin.RecipePreviewPrefabBlacklist, ref _recipePreviewPrefabBlacklistRaw, ref _recipePreviewPrefabBlacklist); } private static HashSet<string> GetPiecePreviewPrefabBlacklist() { return GetPrefabBlacklist(VeiledRecipesPlugin.PiecePreviewPrefabBlacklist, ref _piecePreviewPrefabBlacklistRaw, ref _piecePreviewPrefabBlacklist); } private static bool HasPreviewBlacklistedRequirement(Requirement[] requirements) { HashSet<string> requirementPreviewPrefabBlacklist = GetRequirementPreviewPrefabBlacklist(); if (requirementPreviewPrefabBlacklist.Count == 0 || requirements == null) { return false; } foreach (Requirement val in requirements) { if (!((Object)(object)val?.m_resItem == (Object)null) && val.GetAmount(1) > 0) { string[] obj = new string[3] { ((Object)val.m_resItem).name, null, null }; GameObject gameObject = ((Component)val.m_resItem).gameObject; obj[1] = ((gameObject != null) ? ((Object)gameObject).name : null); GameObject dropPrefab = val.m_resItem.m_itemData.m_dropPrefab; obj[2] = ((dropPrefab != null) ? ((Object)dropPrefab).name : null); if (ContainsPrefabName(requirementPreviewPrefabBlacklist, obj)) { return true; } } } return false; } private static HashSet<string> GetRequirementPreviewPrefabBlacklist() { return GetPrefabBlacklist(VeiledRecipesPlugin.RequirementPreviewPrefabBlacklist, ref _requirementPreviewPrefabBlacklistRaw, ref _requirementPreviewPrefabBlacklist); } private static HashSet<string> GetPrefabBlacklist(ConfigEntry<string> entry, ref string cachedRaw, ref HashSet<string> cached) { string text = entry?.Value ?? ""; if (string.Equals(text, cachedRaw, StringComparison.Ordinal)) { return cached; } cachedRaw = text; cached = new HashSet<string>(StringComparer.OrdinalIgnoreCase); string[] array = text.Split(VeiledRecipeConstants.PrefabBlacklistSeparators, StringSplitOptions.RemoveEmptyEntries); for (int i = 0; i < array.Length; i++) { string text2 = NormalizePrefabName(array[i]); if (!string.IsNullOrEmpty(text2)) { cached.Add(text2); } } return cached; } private static bool ContainsPrefabName(HashSet<string> blacklist, params string?[] names) { for (int i = 0; i < names.Length; i++) { string text = NormalizePrefabName(names[i]); if (!string.IsNullOrEmpty(text) && blacklist.Contains(text)) { return true; } } return false; } private static string NormalizePrefabName(string? name) { string text = name?.Trim() ?? ""; if (text.Length == 0) { return ""; } if (text.EndsWith("(Clone)", StringComparison.OrdinalIgnoreCase)) { text = text.Substring(0, text.Length - "(Clone)".Length).Trim(); } return text; } internal static bool CanPreviewRecipe(Player player, Recipe recipe) { if (!ShowUnknownCraftingRecipes || (Object)(object)player == (Object)null || (Object)(object)recipe == (Object)null || (Object)(object)recipe.m_item == (Object)null) { return false; } if (IsRecipeActuallyKnown(player, recipe) || !IsRecipeEnabledForPlayer(player, recipe)) { return false; } if (IsRecipePreviewBlacklisted(recipe)) { return false; } if (HasPreviewBlacklistedRequirement(recipe.m_resources)) { return false; } if (!DlcInstalled(recipe.m_item.m_itemData.m_shared.m_dlc)) { return false; } if (!PassesCraftFilter(recipe)) { return false; } bool flag = RequireStationLevelForUnknownCraftingRecipes && (Object)(object)recipe.GetRequiredStation(1) != (Object)null; return player.RequiredCraftingStation(recipe, 1, flag); } internal static bool CanPreviewPiece(Player player, Piece piece) { if (!ShowUnknownBuildPieces || (Object)(object)player == (Object)null || (Object)(object)piece == (Object)null) { return false; } if (IsPieceActuallyKnown(player, piece) || !IsPieceEnabledForPlayer(player, piece)) { return false; } if (IsPiecePreviewBlacklisted(piece)) { return false; } if (HasPreviewBlacklistedRequirement(piece.m_resources)) { return false; } if (RequireStationKnowledgeForUnknownBuildPieces && !KnowsPieceStationRequirement(player, piece)) { return false; } return DlcInstalled(piece.m_dlc); } internal static VeiledRecipeVisibilityState GetRecipeVisibilityState(Player player, Recipe recipe) { if (IsRecipeActuallyKnown(player, recipe)) { return VeiledRecipeVisibilityState.Known; } if (!CanPreviewRecipe(player, recipe)) { return VeiledRecipeVisibilityState.Hidden; } return VeiledRecipeVisibilityState.UnknownPreview; } internal static VeiledRecipeVisibilityState GetPieceVisibilityState(Player player, Piece piece) { if (IsPieceActuallyKnown(player, piece)) { return VeiledRecipeVisibilityState.Known; } if (!CanPreviewPiece(player, piece)) { return VeiledRecipeVisibilityState.Hidden; } return VeiledRecipeVisibilityState.UnknownPreview; } internal static bool IsUnknownRecipePreview(Player player, Recipe recipe) { return GetRecipeVisibilityState(player, recipe) == VeiledRecipeVisibilityState.UnknownPreview; } internal static bool IsUnknownPiecePreview(Player player, Piece piece) { return GetPieceVisibilityState(player, piece) == VeiledRecipeVisibilityState.UnknownPreview; } internal static bool CanDiscoverRecipe(Player player, Recipe recipe) { if ((Object)(object)player == (Object)null || (Object)(object)recipe == (Object)null || (Object)(object)recipe.m_item == (Object)null) { return false; } if ((Object)(object)recipe.m_craftingStation != (Object)null && !HasKnownRecipeStationLevel(player, recipe.m_craftingStation.m_name, recipe.m_minStationLevel)) { return false; } if (!DlcInstalled(recipe.m_item.m_itemData.m_shared.m_dlc)) { return false; } return HasDiscoveredRecipeMaterials(player, recipe); } private static bool HasDiscoveredRecipeMaterials(Player player, Recipe recipe) { bool flag = false; Requirement[] resources = recipe.m_resources; foreach (Requirement val in resources) { if (!((Object)(object)val.m_resItem == (Object)null) && val.m_amount > 0) { bool flag2 = IsMaterialKnown(player, val); if (recipe.m_requireOnlyOneIngredient) { flag = flag || flag2; } else if (!flag2) { return false; } } } return !recipe.m_requireOnlyOneIngredient || flag; } private static bool IsRecipeEnabledForPlayer(Player player, Recipe recipe) { bool flag = (Object)(object)player.CurrentSeason != (Object)null && player.CurrentSeason.Recipes.Contains(recipe); return recipe.m_enabled || flag; } private static bool IsPieceEnabledForPlayer(Player player, Piece piece) { bool flag = (Object)(object)player.CurrentSeason != (Object)null && player.CurrentSeason.Pieces.Contains(((Component)piece).gameObject); return piece.m_enabled || flag; } private static bool PassesCraftFilter(Recipe recipe) { if (Player.s_FilterCraft.Count == 0) { return true; } string text = ((Object)recipe.m_item).name.ToLowerInvariant(); string text2 = recipe.m_item.m_itemData.m_shared.m_name.ToLowerInvariant(); string text3 = Localization.instance.Localize(recipe.m_item.m_itemData.m_shared.m_name).ToLowerInvariant(); foreach (string item in Player.s_FilterCraft) { if (!string.IsNullOrWhiteSpace(item)) { string value = item.ToLowerInvariant(); if (text.Contains(value) || text2.Contains(value) || text3.Contains(value)) { return true; } } } return false; } internal static void RecordStationInteraction(Player player, CraftingStation station) { if ((Object)(object)player == (Object)null || (Object)(object)station == (Object)null) { return; } if (!EnableStationProximityDiscovery) { player.AddKnownStation(station); } int num = Math.Max(1, station.GetLevel(true)); string key = StationInteractionKey(station.m_name); if (player.m_customData.TryGetValue(key, out var value) && int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result) && result >= num) { return; } player.m_customData[key] = num.ToString(CultureInfo.InvariantCulture); try { player.UpdateKnownRecipesList(); } catch (Exception ex) { VeiledRecipesPlugin.PluginLogger.LogDebug((object)("Could not refresh known recipes after station interaction: " + ex.Message)); } } internal static bool KnowsRecipeStationRequirement(Player player, Recipe recipe, int quality) { if ((Object)(object)player == (Object)null || (Object)(object)recipe == (Object)null) { return false; } CraftingStation requiredStation = recipe.GetRequiredStation(quality); if (!((Object)(object)requiredStation == (Object)null)) { return HasKnownRecipeStationLevel(player, requiredStation.m_name, recipe.GetRequiredStationLevel(quality)); } return true; } internal static bool KnowsPieceStationRequirement(Player player, Piece piece) { if ((Object)(object)player == (Object)null || (Object)(object)piece == (Object)null || (Object)(object)piece.m_craftingStation == (Object)null) { return true; } return GetKnownPieceStationLevel(player, piece.m_craftingStation.m_name) > 0; } private static int GetKnownRecipeStationLevel(Player player, string stationName) { if ((Object)(object)player == (Object)null || string.IsNullOrEmpty(stationName)) { return 0; } if (RequireStationInteractionForRecipeUnlock) { return GetInteractedStationLevel(player, stationName); } return GetKnownPieceStationLevel(player, stationName); } private static int GetKnownPieceStationLevel(Player player, string stationName) { if ((Object)(object)player == (Object)null || string.IsNullOrEmpty(stationName)) { return 0; } if (!player.m_knownStations.TryGetValue(stationName, out var value)) { return 0; } return value; } private static bool HasKnownRecipeStationLevel(Player player, string stationName, int requiredLevel) { return GetKnownRecipeStationLevel(player, stationName) >= Math.Max(1, requiredLevel); } private static int GetInteractedStationLevel(Player player, string stationName) { if (player.m_customData.TryGetValue(StationInteractionKey(stationName), out var value) && int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out var result)) { return result; } return 0; } private static string StationInteractionKey(string stationName) { return "VeiledRecipes.InteractedStation." + stationName; } } [HarmonyPatch(typeof(MessageHud), "QueueUnlockMsg")] internal static class MessageHudQueueUnlockMsgPatch { private static bool Prefix(MessageHud __instance, string topic, string description) { if (VeiledRecipeState.ShouldShowUnlockNotification(topic)) { return true; } __instance.AddLog(topic + ": " + description); return false; } } [HarmonyPatch(typeof(Player), "OnSkillLevelup")] internal static class PlayerSkillLevelUpEffectsPatch { private static bool Prefix() { return VeiledRecipeState.ShouldShowSkillLevelUpEffect(); } } [HarmonyPatch(typeof(Player), "Message")] internal static class PlayerSkillNotificationAlarmPatch { private static bool Prefix(string msg) { return VeiledRecipeState.ShouldShowSkillLevelUpNotification(msg); } } [HarmonyPatch(typeof(Player), "HaveRequirements", new Type[] { typeof(Piece), typeof(RequirementMode) })] internal static class PlayerPieceRequirementsPatch { private static bool Prefix(Player __instance, Piece piece, RequirementMode mode, ref bool __result) { //IL_000b: Unknown result type (might be due to invalid IL or missing references) //IL_000d: Invalid comparison between Unknown and I4 if ((Object)(object)piece == (Object)null) { return true; } if ((int)mode != 1 && !VeiledRecipeState.IsPieceActuallyKnown(__instance, piece)) { __result = false; return false; } return true; } } [HarmonyPatch(typeof(PieceTable), "UpdateAvailable")] internal static class PieceTableUpdateAvailablePatch { private static void Postfix(PieceTable __instance, Player player) { if (!VeiledRecipeState.ShowUnknownBuildPieces || (Object)(object)player == (Object)null) { return; } EnsureAvailablePieceBuckets(__instance); HashSet<string> availablePiecePrefabNames = GetAvailablePiecePrefabNames(__instance); foreach (GameObject piece in __instance.m_pieces) { Piece val = (((Object)(object)piece == (Object)null) ? null : piece.GetComponent<Piece>()); string text = (((Object)(object)val == (Object)null) ? "" : Utils.GetPrefabName(((Component)val).gameObject)); if (!((Object)(object)val == (Object)null) && !string.IsNullOrEmpty(text) && !availablePiecePrefabNames.Contains(text) && VeiledRecipeState.GetPieceVisibilityState(player, val) == VeiledRecipeVisibilityState.UnknownPreview) { AddAvailablePiece(__instance, val); availablePiecePrefabNames.Add(text); } } } private static void EnsureAvailablePieceBuckets(PieceTable table) { int requiredCategoryBucketCount = GetRequiredCategoryBucketCount(table); while (table.m_availablePieces.Count < requiredCategoryBucketCount) { table.m_availablePieces.Add(new List<Piece>()); } } private static int GetRequiredCategoryBucketCount(PieceTable table) { //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Invalid comparison between Unknown and I4 //IL_0049: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Expected I4, but got Unknown int num = table.m_availablePieces.Count; foreach (GameObject piece in table.m_pieces) { Piece val = (((Object)(object)piece == (Object)null) ? null : piece.GetComponent<Piece>()); if (!((Object)(object)val == (Object)null) && (int)val.m_category != 100) { int num2 = (int)val.m_category; if (num2 >= 0) { num = Math.Max(num, num2 + 1); } } } return num; } private static HashSet<string> GetAvailablePiecePrefabNames(PieceTable table) { HashSet<string> hashSet = new HashSet<string>(); foreach (List<Piece> availablePiece in table.m_availablePieces) { foreach (Piece item in availablePiece) { if ((Object)(object)item != (Object)null) { hashSet.Add(Utils.GetPrefabName(((Component)item).gameObject)); } } } return hashSet; } private static void AddAvailablePiece(PieceTable table, Piece piece) { //IL_0001: Unknown result type (might be due to invalid IL or missing references) //IL_0008: Invalid comparison between Unknown and I4 //IL_0040: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Expected I4, but got Unknown if ((int)piece.m_category == 100) { foreach (List<Piece> availablePiece in table.m_availablePieces) { availablePiece.Add(piece); } return; } int num = (int)piece.m_category; if (num >= 0 && num < table.m_availablePieces.Count) { table.m_availablePieces[num].Add(piece); } } } [HarmonyPatch(typeof(Hud), "UpdatePieceList")] internal static class HudUpdatePieceListPatch { private static void Postfix(Hud __instance, Player player) { //IL_005a: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)player == (Object)null) { return; } List<Piece> buildPieces = player.GetBuildPieces(); if (buildPieces == null) { return; } for (int i = 0; i < __instance.m_pieceIcons.Count && i < buildPieces.Count; i++) { Piece val = buildPieces[i]; if (VeiledRecipeState.GetPieceVisibilityState(player, val) != VeiledRecipeVisibilityState.Known) { PieceIconData obj = __instance.m_pieceIcons[i]; ((Behaviour)obj.m_icon).enabled = true; obj.m_icon.sprite = val.m_icon; ((Graphic)obj.m_icon).color = Color.black; obj.m_tooltip.m_text = VeiledRecipeState.UnknownNameText; obj.m_upgrade.SetActive(false); } } } } [HarmonyPatch(typeof(Hud), "SetupPieceInfo")] internal static class HudSetupPieceInfoPatch { private static void Postfix(Hud __instance, Piece piece) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) if (!((Object)(object)piece == (Object)null) && !((Object)(object)Player.m_localPlayer == (Object)null)) { if (VeiledRecipeState.GetPieceVisibilityState(Player.m_localPlayer, piece) == VeiledRecipeVisibilityState.Known) { ((Graphic)__instance.m_buildIcon).color = Color.white; } else { MaskPieceInfo(__instance, Player.m_localPlayer, piece); } } } private static void MaskPieceInfo(Hud hud, Player player, Piece piece) { //IL_0043: Unknown result type (might be due to invalid IL or missing references) //IL_00b0: Unknown result type (might be due to invalid IL or missing references) //IL_00b7: Invalid comparison between Unknown and I4 hud.m_buildSelection.text = VeiledRecipeState.UnknownNameText; hud.m_pieceDescription.text = VeiledRecipeState.UnknownDescriptionText; ((Behaviour)hud.m_buildIcon).enabled = true; hud.m_buildIcon.sprite = piece.m_icon; ((Graphic)hud.m_buildIcon).color = Color.black; ((Behaviour)hud.m_snappingIcon).enabled = false; int i = 0; Requirement[] resources = piece.m_resources; foreach (Requirement val in resources) { if (i >= hud.m_requirementItems.Length) { break; } if (!((Object)(object)val?.m_resItem == (Object)null) && val.m_amount > 0) { hud.m_requirementItems[i].SetActive(true); VeiledRecipeRequirementUi.SetupRequirement(hud.m_requirementItems[i].transform, val, player, (int)piece.FreeBuildKey() == 20, 0); i++; } } if ((Object)(object)piece.m_craftingStation != (Object)null && i < hud.m_requirementItems.Length) { hud.m_requirementItems[i].SetActive(true); VeiledRecipeRequirementUi.SetupPieceStationRequirement(hud.m_requirementItems[i].transform, player, piece); i++; } for (; i < hud.m_requirementItems.Length; i++) { hud.m_requirementItems[i].SetActive(false); } } } [HarmonyPatch(typeof(Player), "SetupPlacementGhost")] internal static class PlayerSetupPlacementGhostPatch { private static void Postfix(Player __instance) { Piece selectedPiece = __instance.GetSelectedPiece(); if (!((Object)(object)selectedPiece == (Object)null) && !VeiledRecipeState.IsPieceActuallyKnown(__instance, selectedPiece)) { if ((Object)(object)__instance.m_placementGhost != (Object)null) { Object.Destroy((Object)(object)__instance.m_placementGhost); __instance.m_placementGhost = null; } if ((Object)(object)__instance.m_placementMarkerInstance != (Object)null) { __instance.m_placementMarkerInstance.SetActive(false); } } } } [HarmonyPatch(typeof(Player), "TryPlacePiece")] internal static class PlayerTryPlacePiecePatch { private static bool Prefix(Player __instance, Piece piece, ref bool __result) { if ((Object)(object)piece != (Object)null && !VeiledRecipeState.IsPieceActuallyKnown(__instance, piece)) { ((Character)__instance).Message((MessageType)2, "$msg_missingrequirement", 0, (Sprite)null); __result = false; return false; } return true; } } [HarmonyPatch(typeof(Player), "HaveRequirements", new Type[] { typeof(Recipe), typeof(bool), typeof(int), typeof(int) })] internal static class PlayerRecipeRequirementsPatch { private static bool Prefix(Player __instance, Recipe recipe, bool discover, ref bool __result) { if ((Object)(object)recipe == (Object)null) { return true; } if (discover && VeiledRecipeState.RequireStationInteractionForRecipeUnlock) { __result = VeiledRecipeState.CanDiscoverRecipe(__instance, recipe); return false; } if (!discover && !VeiledRecipeState.IsRecipeActuallyKnown(__instance, recipe)) { __result = false; return false; } return true; } } [HarmonyPatch(typeof(Player), "GetAvailableRecipes")] internal static class PlayerGetAvailableRecipesPatch { private static void Postfix(Player __instance, ref List<Recipe> available) { if (!VeiledRecipeState.ShowUnknownCraftingRecipes || (Object)(object)ObjectDB.instance == (Object)null) { return; } HashSet<Recipe> hashSet = new HashSet<Recipe>(available); foreach (Recipe recipe in ObjectDB.instance.m_recipes) { if (!hashSet.Contains(recipe) && VeiledRecipeState.GetRecipeVisibilityState(__instance, recipe) == VeiledRecipeVisibilityState.UnknownPreview) { available.Add(recipe); hashSet.Add(recipe); } } } } [HarmonyPatch(typeof(InventoryGui), "AddRecipeToList")] internal static class InventoryGuiAddRecipeToListPatch { private static void Prefix(Player player, Recipe recipe, ref bool canCraft) { if (!VeiledRecipeState.IsRecipeActuallyKnown(player, recipe)) { canCraft = false; } } private static void Postfix(InventoryGui __instance, Player player, Recipe recipe, ItemData item) { if (!VeiledRecipeState.IsRecipeActuallyKnown(player, recipe)) { GameObject val = FindRecipeElement(__instance, recipe, item); if ((Object)(object)val != (Object)null) { MaskRecipeListElement(val, recipe); } } } private static GameObject? FindRecipeElement(InventoryGui gui, Recipe recipe, ItemData item) { //IL_0017: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) for (int num = gui.m_availableRecipes.Count - 1; num >= 0; num--) { RecipeDataPair val = gui.m_availableRecipes[num]; if ((Object)(object)((RecipeDataPair)(ref val)).Recipe == (Object)(object)recipe && ((RecipeDataPair)(ref val)).ItemData == item) { return ((RecipeDataPair)(ref val)).InterfaceElement; } } return null; } private static void MaskRecipeListElement(GameObject element, Recipe recipe) { //IL_007c: Unknown result type (might be due to invalid IL or missing references) //IL_004c: Unknown result type (might be due to invalid IL or missing references) Image val = VeiledRecipeRequirementUi.FindComponent<Image>(element.transform, "icon"); if ((Object)(object)val != (Object)null) { ((Behaviour)val).enabled = true; if ((Object)(object)recipe?.m_item != (Object)null) { val.sprite = recipe.m_item.m_itemData.GetIcon(); } ((Graphic)val).color = Color.black; } TMP_Text val2 = VeiledRecipeRequirementUi.FindComponent<TMP_Text>(element.transform, "name"); if ((Object)(object)val2 != (Object)null) { val2.text = VeiledRecipeState.UnknownNameText; ((Graphic)val2).color = Color.white; } GuiBar val3 = VeiledRecipeRequirementUi.FindComponent<GuiBar>(element.transform, "Durability"); if ((Object)(object)val3 != (Object)null) { ((Component)val3).gameObject.SetActive(false); } TMP_Text val4 = VeiledRecipeRequirementUi.FindComponent<TMP_Text>(element.transform, "QualityLevel"); if ((Object)(object)val4 != (Object)null) { ((Component)val4).gameObject.SetActive(false); } } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipeList", new Type[] { typeof(List<Recipe>) })] internal static class InventoryGuiUpdateRecipeListPatch { private static void Postfix(InventoryGui __instance) { //IL_0041: Unknown result type (might be due to invalid IL or missing references) //IL_0046: Unknown result type (might be due to invalid IL or missing references) //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0058: Unknown result type (might be due to invalid IL or missing references) //IL_00c3: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00f3: Unknown result type (might be due to invalid IL or missing references) Player localPlayer = Player.m_localPlayer; if (!VeiledRecipeState.GroupUnknownRecipePreviewsBelowKnownRecipes || (Object)(object)localPlayer == (Object)null || __instance.m_availableRecipes.Count <= 1) { return; } List<RecipeDataPair> list = new List<RecipeDataPair>(); List<RecipeDataPair> list2 = new List<RecipeDataPair>(); foreach (RecipeDataPair availableRecipe in __instance.m_availableRecipes) { RecipeDataPair current = availableRecipe; if (VeiledRecipeState.IsUnknownRecipePreview(localPlayer, ((RecipeDataPair)(ref current)).Recipe)) { list2.Add(current); } else { list.Add(current); } } if (list.Count == 0 || list2.Count == 0) { return; } __instance.m_availableRecipes.Clear(); __instance.m_availableRecipes.AddRange(list); __instance.m_availableRecipes.AddRange(list2); for (int i = 0; i < __instance.m_availableRecipes.Count; i++) { RecipeDataPair val = __instance.m_availableRecipes[i]; Transform transform = ((RecipeDataPair)(ref val)).InterfaceElement.transform; RectTransform val2 = (RectTransform)(object)((transform is RectTransform) ? transform : null); if (val2 != null) { val2.anchoredPosition = new Vector2(0f, (float)i * (0f - __instance.m_recipeListSpace)); } } } } [HarmonyPatch(typeof(InventoryGui), "UpdateRecipe")] internal static class InventoryGuiUpdateRecipePatch { private static void Postfix(InventoryGui __instance, Player player) { //IL_0025: Unknown result type (might be due to invalid IL or missing references) Recipe recipe = ((RecipeDataPair)(ref __instance.m_selectedRecipe)).Recipe; if (!((Object)(object)recipe == (Object)null)) { if (VeiledRecipeState.IsRecipeActuallyKnown(player, recipe)) { ((Graphic)__instance.m_recipeIcon).color = Color.white; } else { MaskSelectedRecipe(__instance, player, recipe); } } } private static void MaskSelectedRecipe(InventoryGui gui, Player player, Recipe recipe) { //IL_00ae: Unknown result type (might be due to invalid IL or missing references) ItemData itemData = ((RecipeDataPair)(ref gui.m_selectedRecipe)).ItemData; int num = ((itemData == null) ? 1 : (itemData.m_quality + 1)); int craftMultiplier = ((itemData != null || (!ZInput.GetButton("AltPlace") && !ZInput.GetButton("JoyLStick"))) ? 1 : gui.m_multiCraftAmount); bool allowedQuality = (Object)(object)recipe.m_item != (Object)null && num <= recipe.m_item.m_itemData.m_shared.m_maxQuality; ((Behaviour)gui.m_recipeIcon).enabled = true; if ((Object)(object)recipe.m_item != (Object)null) { gui.m_recipeIcon.sprite = recipe.m_item.m_itemData.GetIcon(); } ((Graphic)gui.m_recipeIcon).color = Color.black; ((Behaviour)gui.m_recipeName).enabled = true; gui.m_recipeName.text = VeiledRecipeState.UnknownNameText; ((Behaviour)gui.m_recipeDecription).enabled = true; gui.m_recipeDecription.text = VeiledRecipeState.UnknownDescriptionText; ((Component)gui.m_variantButton).gameObject.SetActive(false); ((Component)gui.m_itemCraftType).gameObject.SetActive(false); ((Component)gui.m_qualityPanel).gameObject.SetActive(false); ((Selectable)gui.m_craftButton).interactable = false; ((Component)gui.m_craftButton).GetComponent<UITooltip>().m_text = VeiledRecipeState.UnknownDescriptionText; SetupRecipeRequirements(gui, player, recipe, num, allowedQuality, craftMultiplier); SetupRecipeStationLevel(gui, player, recipe, num, allowedQuality); } private static void SetupRecipeStationLevel(InventoryGui gui, Player player, Recipe recipe, int quality, bool allowedQuality) { //IL_0068: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)recipe.GetRequiredStation(quality) == (Object)null || !allowedQuality) { ((Component)gui.m_minStationLevelIcon).gameObject.SetActive(false); return; } ((Component)gui.m_minStationLevelIcon).gameObject.SetActive(true); gui.m_minStationLevelText.text = (VeiledRecipeState.KnowsRecipeStationRequirement(player, recipe, quality) ? recipe.GetRequiredStationLevel(quality).ToString() : VeiledRecipeState.UnknownRequirementText); ((Graphic)gui.m_minStationLevelText).color = gui.m_minStationLevelBasecolor; } private static void SetupRecipeRequirements(InventoryGui gui, Player player, Recipe recipe, int quality, bool allowedQuality, int craftMultiplier) { int i = 0; List<Requirement> visibleRequirements = VeiledRecipeRequirementUi.GetVisibleRequirements(recipe.m_resources, quality); int cyclingStart = VeiledRecipeRequirementUi.GetCyclingStart(visibleRequirements.Count, gui.m_recipeRequirementList.Length); if (allowedQuality) { for (int j = cyclingStart; j < visibleRequirements.Count; j++) { if (i >= gui.m_recipeRequirementList.Length) { break; } VeiledRecipeRequirementUi.SetupRequirement(gui.m_recipeRequirementList[i].transform, visibleRequirements[j], player, craft: true, quality, craftMultiplier); i++; } } for (; i < gui.m_recipeRequirementList.Length; i++) { InventoryGui.HideRequirement(gui.m_recipeRequirementList[i].transform); } } } [HarmonyPatch(typeof(InventoryGui), "OnCraftPressed")] internal static class InventoryGuiOnCraftPressedPatch { private static bool Prefix(InventoryGui __instance) { Recipe recipe = ((RecipeDataPair)(ref __instance.m_selectedRecipe)).Recipe; Player localPlayer = Player.m_localPlayer; if ((Object)(object)recipe != (Object)null && (Object)(object)localPlayer != (Object)null && !VeiledRecipeState.IsRecipeActuallyKnown(localPlayer, recipe)) { ((Character)localPlayer).Message((MessageType)2, "$msg_missingrequirement", 0, (Sprite)null); return false; } return true; } } [HarmonyPatch(typeof(InventoryGui), "DoCrafting")] internal static class InventoryGuiDoCraftingPatch { private static bool Prefix(InventoryGui __instance, Player player) { Recipe craftRecipe = __instance.m_craftRecipe; if ((Object)(object)craftRecipe != (Object)null && !VeiledRecipeState.IsRecipeActuallyKnown(player, craftRecipe)) { __instance.m_craftTimer = -1f; ((Character)player).Message((MessageType)2, "$msg_missingrequirement", 0, (Sprite)null); return false; } return true; } } [HarmonyPatch(typeof(CraftingStation), "Interact")] internal static class CraftingStationInteractPatch { private static void Prefix(CraftingStation __instance, Humanoid user, bool repeat) { if (!repeat && !((Object)(object)user != (Object)(object)Player.m_localPlayer)) { Player val = (Player)(object)((user is Player) ? user : null); if (val != null && __instance.InUseDistance(user) && __instance.CheckUsable(val, false)) { VeiledRecipeState.RecordStationInteraction(val, __instance); } } } } [HarmonyPatch(typeof(CraftingStation), "UpdateKnownStationsInRange")] internal static class CraftingStationUpdateKnownStationsInRangePatch { private static bool Prefix() { return VeiledRecipeState.EnableStationProximityDiscovery; } } public enum VeiledRecipeVisibilityState { Hidden, Known, UnknownPreview } internal static class VeiledRecipeRequirementUi { internal static void SetupRequirement(Transform root, Requirement? requirement, Player player, bool craft, int quality, int multiplier = 1) { if (requirement == null || (Object)(object)requirement.m_resItem == (Object)null || requirement.GetAmount(quality) <= 0) { InventoryGui.HideRequirement(root); } else if (VeiledRecipeState.IsMaterialKnown(player, requirement)) { InventoryGui.SetupRequirement(root, requirement, player, craft, quality, multiplier); } else { SetupMaskedRequirement(root, requirement); } } internal static void SetupMaskedRequirement(Transform root, Requirement requirement) { //IL_005e: 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_00b4: Unknown result type (might be due to invalid IL or missing references) Image val = VeiledRecipeRequirementUi.FindComponent<Image>(root, "res_icon"); TMP_Text val2 = VeiledRecipeRequirementUi.FindComponent<TMP_Text>(root, "res_name"); TMP_Text val3 = VeiledRecipeRequirementUi.FindComponent<TMP_Text>(root, "res_amount"); UITooltip component = ((Component)root).GetComponent<UITooltip>(); if ((Object)(object)val != (Object)null) { ((Component)val).gameObject.SetActive(true); ((Behaviour)val).enabled = true; val.sprite = requirement.m_resItem.m_itemData.GetIcon(); ((Graphic)val).color = Color.black; } if ((Object)(object)val2 != (Object)null) { ((Component)val2).gameObject.SetActive(true); val2.text = VeiledRecipeState.UnknownNameText; ((Graphic)val2).color = Color.white; } if ((Object)(object)val3 != (Object)null) { ((Component)val3).gameObject.SetActive(true); val3.text = VeiledRecipeState.UnknownRequirementText; ((Graphic)val3).color = Color.white; } if ((Object)(object)component != (Object)null) { component.m_text = VeiledRecipeState.UnknownNameText; } } internal static void SetupPieceStationRequirement(Transform root, Player player, Piece piece) { //IL_0082: Unknown result type (might be due to invalid IL or missing references) //IL_007b: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)piece.m_craftingStation == (Object)null) { InventoryGui.HideRequirement(root); return; } Image val = VeiledRecipeRequirementUi.FindComponent<Image>(root, "res_icon"); TMP_Text val2 = VeiledRecipeRequirementUi.FindComponent<TMP_Text>(root, "res_name"); TMP_Text val3 = VeiledRecipeRequirementUi.FindComponent<TMP_Text>(root, "res_amount"); UITooltip component = ((Component)root).GetComponent<UITooltip>(); bool flag = VeiledRecipeState.KnowsPieceStationRequirement(player, piece); if ((Object)(object)val != (Object)null) { ((Component)val).gameObject.SetActive(true); ((Behaviour)val).enabled = true; val.sprite = piece.m_craftingStation.m_icon; ((Graphic)val).color = (flag ? Color.white : Color.black); } if ((Object)(object)val2 != (Object)null) { ((Component)val2).gameObject.SetActive(true); val2.text = (flag ? Localization.instance.Localize(piece.m_craftingStation.m_name) : VeiledRecipeState.UnknownNameText); ((Graphic)val2).color = Color.white; } if ((Object)(object)val3 != (Object)null) { ((Component)val3).gameObject.SetActive(true); SetupStationAmount(val3, player, piece, flag); } if ((Object)(object)component != (Object)null) { component.m_text = (flag ? piece.m_craftingStation.m_name : VeiledRecipeState.UnknownNameText); } } internal static List<Requirement> GetVisibleRequirements(Requirement[]? source, int quality) { List<Requirement> list = new List<Requirement>(); if (source == null) { return list; } foreach (Requirement val in source) { if ((Object)(object)val?.m_resItem != (Object)null && val.GetAmount(quality) > 0) { list.Add(val); } } return list; } internal static int GetCyclingStart(int requirementCount, int slotCount) { if (slotCount <= 0 || requirementCount <= slotCount) { return 0; } int num = Mathf.CeilToInt((float)requirementCount / (float)slotCount); return (int)Time.fixedTime % num * slotCount; } internal static T? FindComponent<T>(Transform root, string childName) where T : Component { Transform val = root.Find(childName); if (!((Object)(object)val == (Object)null)) { return ((Component)val).GetComponent<T>(); } return default(T); } private static void SetupStationAmount(TMP_Text amount, Player player, Piece piece, bool knownStation) { //IL_002b: Unknown result type (might be due to invalid IL or missing references) //IL_000f: 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_0051: Unknown result type (might be due to invalid IL or missing references) if (!knownStation) { amount.text = VeiledRecipeState.UnknownRequirementText; ((Graphic)amount).color = Color.white; return; } CraftingStation val = CraftingStation.HaveBuildStationInRange(piece.m_craftingStation.m_name, ((Component)player).transform.position); if ((Object)(object)val != (Object)null) { val.ShowAreaMarker(); amount.text = ""; ((Graphic)amount).color = Color.white; } else { amount.text = Localization.instance.Localize("$menu_none"); ((Graphic)amount).color = Color.white; } } } public static class VeiledRecipesCompat { public const string PluginGuid = "sighsorry.VeiledRecipes"; public const string PluginName = "VeiledRecipes"; public const string PluginVersion = "1.0.4"; public const string Author = "sighsorry"; public static string UnknownNameText => VeiledRecipeState.UnknownNameText; public static string UnknownDescriptionText => VeiledRecipeState.UnknownDescriptionText; public static string UnknownRequirementText => VeiledRecipeState.UnknownRequirementText; public static bool GroupUnknownRecipePreviewsBelowKnownRecipes => VeiledRecipeState.GroupUnknownRecipePreviewsBelowKnownRecipes; public static VeiledRecipeVisibilityState GetRecipeVisibilityState(Recipe recipe) { return GetRecipeVisibilityState(Player.m_localPlayer, recipe); } public static VeiledRecipeVisibilityState GetRecipeVisibilityState(Player player, Recipe recipe) { if (!((Object)(object)player != (Object)null) || !((Object)(object)recipe != (Object)null)) { return VeiledRecipeVisibilityState.Hidden; } return VeiledRecipeState.GetRecipeVisibilityState(player, recipe); } public static bool IsUnknownRecipePreview(Recipe recipe) { return IsUnknownRecipePreview(Player.m_localPlayer, recipe); } public static bool IsUnknownRecipePreview(Player player, Recipe recipe) { if ((Object)(object)player != (Object)null && (Object)(object)recipe != (Object)null) { return VeiledRecipeState.IsUnknownRecipePreview(player, recipe); } return false; } public static VeiledRecipeVisibilityState GetPieceVisibilityState(Piece piece) { return GetPieceVisibilityState(Player.m_localPlayer, piece); } public static VeiledRecipeVisibilityState GetPieceVisibilityState(Player player, Piece piece) { if (!((Object)(object)player != (Object)null) || !((Object)(object)piece != (Object)null)) { return VeiledRecipeVisibilityState.Hidden; } return VeiledRecipeState.GetPieceVisibilityState(player, piece); } public static bool IsUnknownPiecePreview(Piece piece) { return IsUnknownPiecePreview(Player.m_localPlayer, piece); } public static bool IsUnknownPiecePreview(Player player, Piece piece) { if ((Object)(object)player != (Object)null && (Object)(object)piece != (Object)null) { return VeiledRecipeState.IsUnknownPiecePreview(player, piece); } return false; } public static bool ShouldMaskRecipe(Recipe recipe) { return ShouldMaskRecipe(Player.m_localPlayer, recipe); } public static bool ShouldMaskRecipe(Player player, Recipe recipe) { if ((Object)(object)player != (Object)null && (Object)(object)recipe != (Object)null) { return !VeiledRecipeState.IsRecipeActuallyKnown(player, recipe); } return false; } public static bool ShouldMaskRecipePair(RecipeDataPair pair) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) return ShouldMaskRecipePair(Player.m_localPlayer, pair); } public static bool ShouldMaskRecipePair(Player player, RecipeDataPair pair) { return ShouldMaskRecipe(player, ((RecipeDataPair)(ref pair)).Recipe); } public static bool IsRecipeActuallyKnown(Player player, Recipe recipe) { if ((Object)(object)player != (Object)null && (Object)(object)recipe != (Object)null) { return VeiledRecipeState.IsRecipeActuallyKnown(player, recipe); } return false; } public static bool ShouldMaskPiece(Piece piece) { return ShouldMaskPiece(Player.m_localPlayer, piece); } public static bool ShouldMaskPiece(Player player, Piece piece) { if ((Object)(object)player != (Object)null && (Object)(object)piece != (Object)null) { return !VeiledRecipeState.IsPieceActuallyKnown(player, piece); } return false; } public static void RegisterKnownPieceOverride(Func<Piece, bool> predicate) { VeiledRecipeState.RegisterKnownPieceOverride(predicate); } public static void UnregisterKnownPieceOverride(Func<Piece, bool> predicate) { VeiledRecipeState.UnregisterKnownPieceOverride(predicate); } public static void RegisterKnownPiecePrefabOverride(string prefabName) { VeiledRecipeState.RegisterKnownPiecePrefabOverride(prefabName); } public static void UnregisterKnownPiecePrefabOverride(string prefabName) { VeiledRecipeState.UnregisterKnownPiecePrefabOverride(prefabName); } public static void RegisterKnownPieceTypeOverride(string typeName) { VeiledRecipeState.RegisterKnownPieceTypeOverride(typeName); } public static void UnregisterKnownPieceTypeOverride(string typeName) { VeiledRecipeState.UnregisterKnownPieceTypeOverride(typeName); } public static bool IsPieceActuallyKnown(Player player, Piece piece) { if ((Object)(object)player != (Object)null && (Object)(object)piece != (Object)null) { return VeiledRecipeState.IsPieceActuallyKnown(player, piece); } return false; } public static bool IsMaterialKnown(Requirement requirement) { return IsMaterialKnown(Player.m_localPlayer, requirement); } public static bool IsMaterialKnown(Player player, Requirement requirement) { if ((Object)(object)player != (Object)null && requirement != null) { return VeiledRecipeState.IsMaterialKnown(player, requirement); } return false; } public static bool KnowsRecipeStationRequirement(Recipe recipe, int quality) { return KnowsRecipeStationRequirement(Player.m_localPlayer, recipe, quality); } public static bool KnowsRecipeStationRequirement(Player player, Recipe recipe, int quality) { if ((Object)(object)player != (Object)null && (Object)(object)recipe != (Object)null) { return VeiledRecipeState.KnowsRecipeStationRequirement(player, recipe, quality); } return false; } public static bool KnowsPieceStationRequirement(Piece piece) { return KnowsPieceStationRequirement(Player.m_localPlayer, piece); } public static bool KnowsPieceStationRequirement(Player player, Piece piece) { if ((Object)(object)player != (Object)null && (Object)(object)piece != (Object)null) { return VeiledRecipeState.KnowsPieceStationRequirement(player, piece); } return false; } } } namespace ServerSync { [PublicAPI] internal abstract class OwnConfigEntryBase { public object? LocalBaseValue; public bool SynchronizedConfig = true; public abstract ConfigEntryBase BaseConfig { get; } } [PublicAPI] internal class SyncedConfigEntry<T> : OwnConfigEntryBase { public readonly ConfigEntry<T> SourceConfig; public override ConfigEntryBase BaseConfig => (ConfigEntryBase)(object)SourceConfig; public T Value { get { return SourceConfig.Value; } set { SourceConfig.Value = value; } } public SyncedConfigEntry(ConfigEntry<T> sourceConfig) { SourceConfig = sourceConfig; base..ctor(); } public void AssignLocalValue(T value) { if (LocalBaseValue == null) { Value = value; } else { LocalBaseValue = value; } } } internal abstract class CustomSyncedValueBase { public object? LocalBaseValue; public readonly string Identifier; public readonly Type Type; private object? boxedValue; protected bool localIsOwner; public readonly int Priority; public object? BoxedValue { get { return boxedValue; } set { boxedValue = value; this.ValueChanged?.Invoke(); } } public event Action? ValueChanged; protected CustomSyncedValueBase(ConfigSync configSync, string identifier, Type type, int priority) { Priority = priority; Identifier = identifier; Type = type; configSync.AddCustomValue(this); localIsOwner = configSync.IsSourceOfTruth; configSync.SourceOfTruthChanged += delegate(bool truth) { localIsOwner = truth; }; } } [PublicAPI] internal sealed class CustomSyncedValue<T> : CustomSyncedValueBase { public T Value { get { return (T)base.BoxedValue; } set { base.BoxedValue = value; } } public CustomSyncedValue(ConfigSync configSync, string identifier, T value = default(T), int priority = 0) : base(configSync, identifier, typeof(T), priority) { Value = value; } public void AssignLocalValue(T value) { if (localIsOwner) { Value = value; } else { LocalBaseValue = value; } } } internal class ConfigurationManagerAttributes { [UsedImplicitly] public bool? ReadOnly = false; } [PublicAPI] internal class ConfigSync { [HarmonyPatch(typeof(ZRpc), "HandlePackage")] private static class SnatchCurrentlyHandlingRPC { public static ZRpc? currentRpc; [HarmonyPrefix] private static void Prefix(ZRpc __instance) { currentRpc = __instance; } } [HarmonyPatch(typeof(ZNet), "Awake")] internal static class RegisterRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance) { isServer = __instance.IsServer(); foreach (ConfigSync configSync2 in configSyncs) { ZRoutedRpc.instance.Register<ZPackage>(configSync2.Name + " ConfigSync", (Action<long, ZPackage>)configSync2.RPC_FromOtherClientConfigSync); if (isServer) { configSync2.InitialSyncDone = true; Debug.Log((object)("Registered '" + configSync2.Name + " ConfigSync' RPC - waiting for incoming connections")); } } if (isServer) { ((MonoBehaviour)__instance).StartCoroutine(WatchAdminListChanges()); } static void SendAdmin(List<ZNetPeer> peers, bool isAdmin) { ZPackage package = ConfigsToPackage(null, null, new PackageEntry[1] { new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = isAdmin } }); ConfigSync configSync = configSyncs.First(); if (configSync != null) { ((MonoBehaviour)ZNet.instance).StartCoroutine(configSync.sendZPackage(peers, package)); } } static IEnumerator WatchAdminListChanges() { MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); List<string> CurrentList = new List<string>(adminList.GetList()); while (true) { yield return (object)new WaitForSeconds(30f); if (!adminList.GetList().SequenceEqual(CurrentList)) { CurrentList = new List<string>(adminList.GetList()); List<ZNetPeer> adminPeer = ZNet.instance.GetPeers().Where(delegate(ZNetPeer p) { string hostName = p.m_rpc.GetSocket().GetHostName(); return ((object)listContainsId == null) ? adminList.Contains(hostName) : ((bool)listContainsId.Invoke(ZNet.instance, new object[2] { adminList, hostName })); }).ToList(); List<ZNetPeer> nonAdminPeer = ZNet.instance.GetPeers().Except(adminPeer).ToList(); SendAdmin(nonAdminPeer, isAdmin: false); SendAdmin(adminPeer, isAdmin: true); } } } } } [HarmonyPatch(typeof(ZNet), "OnNewConnection")] private static class RegisterClientRPCPatch { [HarmonyPostfix] private static void Postfix(ZNet __instance, ZNetPeer peer) { if (__instance.IsServer()) { return; } foreach (ConfigSync configSync in configSyncs) { peer.m_rpc.Register<ZPackage>(configSync.Name + " ConfigSync", (Action<ZRpc, ZPackage>)configSync.RPC_FromServerConfigSync); } } } private class ParsedConfigs { public readonly Dictionary<OwnConfigEntryBase, object?> configValues = new Dictionary<OwnConfigEntryBase, object>(); public readonly Dictionary<CustomSyncedValueBase, object?> customValues = new Dictionary<CustomSyncedValueBase, object>(); } [HarmonyPatch(typeof(ZNet), "Shutdown")] private class ResetConfigsOnShutdown { [HarmonyPostfix] private static void Postfix() { ProcessingServerUpdate = true; foreach (ConfigSync configSync in configSyncs) { configSync.resetConfigsFromServer(); configSync.IsSourceOfTruth = true; configSync.InitialSyncDone = false; } ProcessingServerUpdate = false; } } [HarmonyPatch(typeof(ZNet), "RPC_PeerInfo")] private class SendConfigsAfterLogin { private class BufferingSocket : ZPlayFabSocket, ISocket { public volatile bool finished = false; public volatile int versionMatchQueued = -1; public readonly List<ZPackage> Package = new List<ZPackage>(); public readonly ISocket Original; public BufferingSocket(ISocket original) { Original = original; ((ZPlayFabSocket)this)..ctor(); } public bool IsConnected() { return Original.IsConnected(); } public ZPackage Recv() { return Original.Recv(); } public int GetSendQueueSize() { return Original.GetSendQueueSize(); } public int GetCurrentSendRate() { return Original.GetCurrentSendRate(); } public bool IsHost() { return Original.IsHost(); } public void Dispose() { Original.Dispose(); } public bool GotNewData() { return Original.GotNewData(); } public void Close() { Original.Close(); } public string GetEndPointString() { return Original.GetEndPointString(); } public void GetAndResetStats(out int totalSent, out int totalRecv) { Original.GetAndResetStats(ref totalSent, ref totalRecv); } public void GetConnectionQuality(out float localQuality, out float remoteQuality, out int ping, out float outByteSec, out float inByteSec) { Original.GetConnectionQuality(ref localQuality, ref remoteQuality, ref ping, ref outByteSec, ref inByteSec); } public ISocket Accept() { return Original.Accept(); } public int GetHostPort() { return Original.GetHostPort(); } public bool Flush() { return Original.Flush(); } public string GetHostName() { return Original.GetHostName(); } public void VersionMatch() { if (finished) { Original.VersionMatch(); } else { versionMatchQueued = Package.Count; } } public void Send(ZPackage pkg) { //IL_0057: Unknown result type (might be due to invalid IL or missing references) //IL_005d: Expected O, but got Unknown int pos = pkg.GetPos(); pkg.SetPos(0); int num = pkg.ReadInt(); if ((num == StringExtensionMethods.GetStableHashCode("PeerInfo") || num == StringExtensionMethods.GetStableHashCode("RoutedRPC") || num == StringExtensionMethods.GetStableHashCode("ZDOData")) && !finished) { ZPackage val = new ZPackage(pkg.GetArray()); val.SetPos(pos); Package.Add(val); } else { pkg.SetPos(pos); Original.Send(pkg); } } } [HarmonyPriority(800)] [HarmonyPrefix] private static void Prefix(ref Dictionary<Assembly, BufferingSocket>? __state, ZNet __instance, ZRpc rpc) { //IL_0078: Unknown result type (might be due to invalid IL or missing references) //IL_007e: Invalid comparison between Unknown and I4 if (!__instance.IsServer()) { return; } BufferingSocket bufferingSocket = new BufferingSocket(rpc.GetSocket()); AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc, bufferingSocket); object? obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance, new object[1] { rpc }); ZNetPeer val = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (val != null && (int)ZNet.m_onlineBackend > 0) { FieldInfo fieldInfo = AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket"); object? value = fieldInfo.GetValue(val); ZPlayFabSocket val2 = (ZPlayFabSocket)((value is ZPlayFabSocket) ? value : null); if (val2 != null) { typeof(ZPlayFabSocket).GetField("m_remotePlayerId").SetValue(bufferingSocket, val2.m_remotePlayerId); } fieldInfo.SetValue(val, bufferingSocket); } if (__state == null) { __state = new Dictionary<Assembly, BufferingSocket>(); } __state[Assembly.GetExecutingAssembly()] = bufferingSocket; } [HarmonyPostfix] private static void Postfix(Dictionary<Assembly, BufferingSocket> __state, ZNet __instance, ZRpc rpc) { ZRpc rpc2 = rpc; ZNet __instance2 = __instance; Dictionary<Assembly, BufferingSocket> __state2 = __state; ZNetPeer peer; if (__instance2.IsServer()) { object obj = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance2, new object[1] { rpc2 }); peer = (ZNetPeer)((obj is ZNetPeer) ? obj : null); if (peer == null) { SendBufferedData(); } else { ((MonoBehaviour)__instance2).StartCoroutine(sendAsync()); } } void SendBufferedData() { if (rpc2.GetSocket() is BufferingSocket bufferingSocket) { AccessTools.DeclaredField(typeof(ZRpc), "m_socket").SetValue(rpc2, bufferingSocket.Original); object? obj2 = AccessTools.DeclaredMethod(typeof(ZNet), "GetPeer", new Type[1] { typeof(ZRpc) }, (Type[])null).Invoke(__instance2, new object[1] { rpc2 }); ZNetPeer val = (ZNetPeer)((obj2 is ZNetPeer) ? obj2 : null); if (val != null) { AccessTools.DeclaredField(typeof(ZNetPeer), "m_socket").SetValue(val, bufferingSocket.Original); } } BufferingSocket bufferingSocket2 = __state2[Assembly.GetExecutingAssembly()]; bufferingSocket2.finished = true; for (int i = 0; i < bufferingSocket2.Package.Count; i++) { if (i == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } bufferingSocket2.Original.Send(bufferingSocket2.Package[i]); } if (bufferingSocket2.Package.Count == bufferingSocket2.versionMatchQueued) { bufferingSocket2.Original.VersionMatch(); } } IEnumerator sendAsync() { foreach (ConfigSync configSync in configSyncs) { List<PackageEntry> entries = new List<PackageEntry>(); if (configSync.CurrentVersion != null) { entries.Add(new PackageEntry { section = "Internal", key = "serverversion", type = typeof(string), value = configSync.CurrentVersion }); } MethodInfo listContainsId = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList adminList = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); entries.Add(new PackageEntry { section = "Internal", key = "lockexempt", type = typeof(bool), value = (((object)listContainsId == null) ? ((object)adminList.Contains(rpc2.GetSocket().GetHostName())) : listContainsId.Invoke(ZNet.instance, new object[2] { adminList, rpc2.GetSocket().GetHostName() })) }); ZPackage package = ConfigsToPackage(configSync.allConfigs.Select((OwnConfigEntryBase c) => c.BaseConfig), configSync.allCustomValues, entries, partial: false); yield return ((MonoBehaviour)__instance2).StartCoroutine(configSync.sendZPackage(new List<ZNetPeer> { peer }, package)); } SendBufferedData(); } } } private class PackageEntry { public string section = null; public string key = null; public Type type = null; public object? value; } [HarmonyPatch(typeof(ConfigEntryBase), "GetSerializedValue")] private static class PreventSavingServerInfo { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, ref string __result) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || isWritableConfig(ownConfigEntryBase)) { return true; } __result = TomlTypeConverter.ConvertToString(ownConfigEntryBase.LocalBaseValue, __instance.SettingType); return false; } } [HarmonyPatch(typeof(ConfigEntryBase), "SetSerializedValue")] private static class PreventConfigRereadChangingValues { [HarmonyPrefix] private static bool Prefix(ConfigEntryBase __instance, string value) { OwnConfigEntryBase ownConfigEntryBase = configData(__instance); if (ownConfigEntryBase == null || ownConfigEntryBase.LocalBaseValue == null) { return true; } try { ownConfigEntryBase.LocalBaseValue = TomlTypeConverter.ConvertToValue(value, __instance.SettingType); } catch (Exception ex) { Debug.LogWarning((object)$"Config value of setting \"{__instance.Definition}\" could not be parsed and will be ignored. Reason: {ex.Message}; Value: {value}"); } return false; } } private class InvalidDeserializationTypeException : Exception { public string expected = null; public string received = null; public string field = ""; } public static bool ProcessingServerUpdate; public readonly string Name; public string? DisplayName; public string? CurrentVersion; public string? MinimumRequiredVersion; public bool ModRequired = false; private bool? forceConfigLocking; private bool isSourceOfTruth = true; private static readonly HashSet<ConfigSync> configSyncs; private readonly HashSet<OwnConfigEntryBase> allConfigs = new HashSet<OwnConfigEntryBase>(); private HashSet<CustomSyncedValueBase> allCustomValues = new HashSet<CustomSyncedValueBase>(); private static bool isServer; private static bool lockExempt; private OwnConfigEntryBase? lockedConfig = null; private const byte PARTIAL_CONFIGS = 1; private const byte FRAGMENTED_CONFIG = 2; private const byte COMPRESSED_CONFIG = 4; private readonly Dictionary<string, SortedDictionary<int, byte[]>> configValueCache = new Dictionary<string, SortedDictionary<int, byte[]>>(); private readonly List<KeyValuePair<long, string>> cacheExpirations = new List<KeyValuePair<long, string>>(); private static long packageCounter; public bool IsLocked { get { bool? flag = forceConfigLocking; bool num; if (!flag.HasValue) { if (lockedConfig == null) { goto IL_0052; } num = ((IConvertible)lockedConfig.BaseConfig.BoxedValue).ToInt32(CultureInfo.InvariantCulture) != 0; } else { num = flag.GetValueOrDefault(); } if (!num) { goto IL_0052; } int result = ((!lockExempt) ? 1 : 0); goto IL_0053; IL_0053: return (byte)result != 0; IL_0052: result = 0; goto IL_0053; } set { forceConfigLocking = value; } } public bool IsAdmin => lockExempt || isSourceOfTruth; public bool IsSourceOfTruth { get { return isSourceOfTruth; } private set { if (value != isSourceOfTruth) { isSourceOfTruth = value; this.SourceOfTruthChanged?.Invoke(value); } } } public bool InitialSyncDone { get; private set; } = false; public event Action<bool>? SourceOfTruthChanged; private event Action? lockedConfigChanged; static ConfigSync() { ProcessingServerUpdate = false; configSyncs = new HashSet<ConfigSync>(); lockExempt = false; packageCounter = 0L; RuntimeHelpers.RunClassConstructor(typeof(VersionCheck).TypeHandle); } public ConfigSync(string name) { Name = name; configSyncs.Add(this); new VersionCheck(this); } public SyncedConfigEntry<T> AddConfigEntry<T>(ConfigEntry<T> configEntry) { ConfigEntry<T> configEntry2 = configEntry; OwnConfigEntryBase ownConfigEntryBase = configData((ConfigEntryBase)(object)configEntry2); SyncedConfigEntry<T> syncedEntry = ownConfigEntryBase as SyncedConfigEntry<T>; if (syncedEntry == null) { syncedEntry = new SyncedConfigEntry<T>(configEntry2); AccessTools.DeclaredField(typeof(ConfigDescription), "<Tags>k__BackingField").SetValue(((ConfigEntryBase)configEntry2).Description, new object[1] { new ConfigurationManagerAttributes() }.Concat(((ConfigEntryBase)configEntry2).Description.Tags ?? Array.Empty<object>()).Concat(new SyncedConfigEntry<T>[1] { syncedEntry }).ToArray()); configEntry2.SettingChanged += delegate { if (!ProcessingServerUpdate && syncedEntry.SynchronizedConfig) { Broadcast(ZRoutedRpc.Everybody, (ConfigEntryBase)configEntry2); } }; allConfigs.Add(syncedEntry); } return syncedEntry; } public SyncedConfigEntry<T> AddLockingConfigEntry<T>(ConfigEntry<T> lockingConfig) where T : IConvertible { if (lockedConfig != null) { throw new Exception("Cannot initialize locking ConfigEntry twice"); } lockedConfig = AddConfigEntry<T>(lockingConfig); lockingConfig.SettingChanged += delegate { this.lockedConfigChanged?.Invoke(); }; return (SyncedConfigEntry<T>)lockedConfig; } internal void AddCustomValue(CustomSyncedValueBase customValue) { CustomSyncedValueBase customValue2 = customValue; if (allCustomValues.Select((CustomSyncedValueBase v) => v.Identifier).Concat(new string[1] { "serverversion" }).Contains(customValue2.Identifier)) { throw new Exception("Cannot have multiple settings with the same name or with a reserved name (serverversion)"); } allCustomValues.Add(customValue2); allCustomValues = new HashSet<CustomSyncedValueBase>(allCustomValues.OrderByDescending((CustomSyncedValueBase v) => v.Priority)); customValue2.ValueChanged += delegate { if (!ProcessingServerUpdate) { Broadcast(ZRoutedRpc.Everybody, customValue2); } }; } private void RPC_FromServerConfigSync(ZRpc rpc, ZPackage package) { lockedConfigChanged += serverLockedSettingChanged; IsSourceOfTruth = false; if (HandleConfigSyncRPC(0L, package, clientUpdate: false)) { InitialSyncDone = true; } } private void RPC_FromOtherClientConfigSync(long sender, ZPackage package) { HandleConfigSyncRPC(sender, package, clientUpdate: true); } private bool HandleConfigSyncRPC(long sender, ZPackage package, bool clientUpdate) { //IL_0076: Unknown result type (might be due to invalid IL or missing references) //IL_007d: Expected O, but got Unknown //IL_0250: Unknown result type (might be due to invalid IL or missing references) //IL_0257: Expected O, but got Unknown //IL_01ea: Unknown result type (might be due to invalid IL or missing references) //IL_01f1: Expected O, but got Unknown try { if (isServer && IsLocked) { ZRpc? currentRpc = SnatchCurrentlyHandlingRPC.currentRpc; object obj; if (currentRpc == null) { obj = null; } else { ISocket socket = currentRpc.GetSocket(); obj = ((socket != null) ? socket.GetHostName() : null); } string text = (string)obj; if (text != null) { MethodInfo methodInfo = AccessTools.DeclaredMethod(typeof(ZNet), "ListContainsId", (Type[])null, (Type[])null); SyncedList val = (SyncedList)AccessTools.DeclaredField(typeof(ZNet), "m_adminList").GetValue(ZNet.instance); if (!(((object)methodInfo == null) ? val.Contains(text) : ((bool)methodInfo.Invoke(ZNet.instance, new object[2] { val, text })))) { return false; } } } cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> kv) { if (kv.Key < DateTimeOffset.Now.Ticks) { configValueCache.Remove(kv.Value); return true; } return false; }); byte b = package.ReadByte(); if ((b & 2u) != 0) { long num = package.ReadLong(); string text2 = sender.ToString() + num; if (!configValueCache.TryGetValue(text2, out SortedDictionary<int, byte[]> value)) { value = new SortedDictionary<int, byte[]>(); configValueCache[text2] = value; cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.AddSeconds(60.0).Ticks, text2)); } int key = package.ReadInt(); int num2 = package.ReadInt(); value.Add(key, package.ReadByteArray()); if (value.Count < num2) { return false; } configValueCache.Remove(text2); package = new ZPackage(value.Values.SelectMany((byte[] a) => a).ToArray()); b = package.ReadByte(); } ProcessingServerUpdate = true; if ((b & 4u) != 0) { byte[] buffer = package.ReadByteArray(); MemoryStream stream = new MemoryStream(buffer); MemoryStream memoryStream = new MemoryStream(); using (DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress)) { deflateStream.CopyTo(memoryStream); } package = new ZPackage(memoryStream.ToArray()); b = package.ReadByte(); } if ((b & 1) == 0) { resetConfigsFromServer(); } ParsedConfigs parsedConfigs = ReadConfigsFromPackage(package); ConfigFile val2 = null; bool saveOnConfigSet = false; foreach (KeyValuePair<OwnConfigEntryBase, object> configValue in parsedConfigs.configValues) { if (!isServer && configValue.Key.LocalBaseValue == null) { configValue.Key.LocalBaseValue = configValue.Key.BaseConfig.BoxedValue; } if (val2 == null) { val2 = configValue.Key.BaseConfig.ConfigFile; saveOnConfigSet = val2.SaveOnConfigSet; val2.SaveOnConfigSet = false; } configValue.Key.BaseConfig.BoxedValue = configValue.Value; } if (val2 != null) { val2.SaveOnConfigSet = saveOnConfigSet; val2.Save(); } foreach (KeyValuePair<CustomSyncedValueBase, object> customValue in parsedConfigs.customValues) { if (!isServer) { CustomSyncedValueBase key2 = customValue.Key; if (key2.LocalBaseValue == null) { key2.LocalBaseValue = customValue.Key.BoxedValue; } } customValue.Key.BoxedValue = customValue.Value; } Debug.Log((object)string.Format("Received {0} configs and {1} custom values from {2} for mod {3}", parsedConfigs.configValues.Count, parsedConfigs.customValues.Count, (isServer || clientUpdate) ? $"client {sender}" : "the server", DisplayName ?? Name)); if (!isServer) { serverLockedSettingChanged(); } return true; } finally { ProcessingServerUpdate = false; } } private ParsedConfigs ReadConfigsFromPackage(ZPackage package) { ParsedConfigs parsedConfigs = new ParsedConfigs(); Dictionary<string, OwnConfigEntryBase> dictionary = allConfigs.Where((OwnConfigEntryBase c) => c.SynchronizedConfig).ToDictionary((OwnConfigEntryBase c) => c.BaseConfig.Definition.Section + "_" + c.BaseConfig.Definition.Key, (OwnConfigEntryBase c) => c); Dictionary<string, CustomSyncedValueBase> dictionary2 = allCustomValues.ToDictionary((CustomSyncedValueBase c) => c.Identifier, (CustomSyncedValueBase c) => c); int num = package.ReadInt(); for (int i = 0; i < num; i++) { string text = package.ReadString(); string text2 = package.ReadString(); string text3 = package.ReadString(); Type type = Type.GetType(text3); if (text3 == "" || type != null) { object obj; try { obj = ((text3 == "") ? null : ReadValueWithTypeFromZPackage(package, type)); } catch (InvalidDeserializationTypeException ex) { Debug.LogWarning((object)("Got unexpected struct internal type " + ex.received + " for field " + ex.field + " struct " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + ex.expected)); continue; } OwnConfigEntryBase value2; if (text == "Internal") { CustomSyncedValueBase value; if (text2 == "serverversion") { if (obj?.ToString() != CurrentVersion) { Debug.LogWarning((object)("Received server version is not equal: server version = " + (obj?.ToString() ?? "null") + "; local version = " + (CurrentVersion ?? "unknown"))); } } else if (text2 == "lockexempt") { if (obj is bool flag) { lockExempt = flag; } } else if (dictionary2.TryGetValue(text2, out value)) { if ((text3 == "" && (!value.Type.IsValueType || Nullable.GetUnderlyingType(value.Type) != null)) || GetZPackageTypeString(value.Type) == text3) { parsedConfigs.customValues[value] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for internal value " + text2 + " for mod " + (DisplayName ?? Name) + ", expecting " + value.Type.AssemblyQualifiedName)); } } else if (dictionary.TryGetValue(text + "_" + text2, out value2)) { Type type2 = configType(value2.BaseConfig); if ((text3 == "" && (!type2.IsValueType || Nullable.GetUnderlyingType(type2) != null)) || GetZPackageTypeString(type2) == text3) { parsedConfigs.configValues[value2] = obj; continue; } Debug.LogWarning((object)("Got unexpected type " + text3 + " for " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ", expecting " + type2.AssemblyQualifiedName)); } else { Debug.LogWarning((object)("Received unknown config entry " + text2 + " in section " + text + " for mod " + (DisplayName ?? Name) + ". This may happen if client and server versions of the mod do not match.")); } continue; } Debug.LogWarning((object)("Got invalid type " + text3 + ", abort reading of received configs")); return new ParsedConfigs(); } return parsedConfigs; } private static bool isWritableConfig(OwnConfigEntryBase config) { OwnConfigEntryBase config2 = config; ConfigSync configSync = configSyncs.FirstOrDefault((ConfigSync cs) => cs.allConfigs.Contains(config2)); if (configSync == null) { return true; } return configSync.IsSourceOfTruth || !config2.SynchronizedConfig || config2.LocalBaseValue == null || (!configSync.IsLocked && (config2 != configSync.lockedConfig || lockExempt)); } private void serverLockedSettingChanged() { foreach (OwnConfigEntryBase allConfig in allConfigs) { configAttribute<ConfigurationManagerAttributes>(allConfig.BaseConfig).ReadOnly = !isWritableConfig(allConfig); } } private void resetConfigsFromServer() { ConfigFile val = null; bool saveOnConfigSet = false; foreach (OwnConfigEntryBase item in allConfigs.Where((OwnConfigEntryBase config) => config.LocalBaseValue != null)) { if (val == null) { val = item.BaseConfig.ConfigFile; saveOnConfigSet = val.SaveOnConfigSet; val.SaveOnConfigSet = false; } item.BaseConfig.BoxedValue = item.LocalBaseValue; item.LocalBaseValue = null; } if (val != null) { val.SaveOnConfigSet = saveOnConfigSet; } foreach (CustomSyncedValueBase item2 in allCustomValues.Where((CustomSyncedValueBase config) => config.LocalBaseValue != null)) { item2.BoxedValue = item2.LocalBaseValue; item2.LocalBaseValue = null; } lockedConfigChanged -= serverLockedSettingChanged; serverLockedSettingChanged(); } private IEnumerator<bool> distributeConfigToPeers(ZNetPeer peer, ZPackage package) { ZNetPeer peer2 = peer; ZRoutedRpc rpc = ZRoutedRpc.instance; if (rpc == null) { yield break; } byte[] data = package.GetArray(); if (data != null && data.LongLength > 250000) { int fragments = (int)(1 + (data.LongLength - 1) / 250000); long packageIdentifier = ++packageCounter; int fragment = 0; while (fragment < fragments) { foreach (bool item in waitForQueue()) { yield return item; } if (peer2.m_socket.IsConnected()) { ZPackage fragmentedPackage = new ZPackage(); fragmentedPackage.Write((byte)2); fragmentedPackage.Write(packageIdentifier); fragmentedPackage.Write(fragment); fragmentedPackage.Write(fragments); fragmentedPackage.Write(data.Skip(250000 * fragment).Take(250000).ToArray()); SendPackage(fragmentedPackage); if (fragment != fragments - 1) { yield return true; } int num = fragment + 1; fragment = num; continue; } break; } yield break; } foreach (bool item2 in waitForQueue()) { yield return item2; } SendPackage(package); void SendPackage(ZPackage pkg) { string text = Name + " ConfigSync"; if (isServer) { peer2.m_rpc.Invoke(text, new object[1] { pkg }); } else { rpc.InvokeRoutedRPC(peer2.m_server ? 0 : peer2.m_uid, text, new object[1] { pkg }); } } IEnumerable<bool> waitForQueue() { float timeout = Time.time + 30f; while (peer2.m_socket.GetSendQueueSize() > 20000) { if (Time.time > timeout) { Debug.Log((object)$"Disconnecting {peer2.m_uid} after 30 seconds config sending timeout"); peer2.m_rpc.Invoke("Error", new object[1] { (object)(ConnectionStatus)5 }); ZNet.instance.Disconnect(peer2); break; } yield return false; } } } private IEnumerator sendZPackage(long target, ZPackage package) { if (!Object.op_Implicit((Object)(object)ZNet.instance)) { return Enumerable.Empty<object>().GetEnumerator(); } List<ZNetPeer> list = (List<ZNetPeer>)AccessTools.DeclaredField(typeof(ZRoutedRpc), "m_peers").GetValue(ZRoutedRpc.instance); if (target != ZRoutedRpc.Everybody) { list = list.Where((ZNetPeer p) => p.m_uid == target).ToList(); } return sendZPackage(list, package); } private IEnumerator sendZPackage(List<ZNetPeer> peers, ZPackage package) { ZPackage package2 = package; if (!Object.op_Implicit((Object)(object)ZNet.instance)) { yield break; } byte[] rawData = package2.GetArray(); if (rawData != null && raw