Decompiled source of FiresAdminPrefabs v1.1.59

FiresAdminPrefabs.dll

Decompiled 2 days ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using Data;
using FiresCore.Bridge;
using FiresCore.Help;
using FiresCore.Input;
using FiresCore.Items;
using FiresCore.Lifecycle;
using FiresCore.Net;
using FiresCore.Services;
using FiresCore.Sync;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using Splatform;
using Unity.Collections;
using UnityEngine;
using UnityEngine.Animations;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.Rendering;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using VerdantsAscent.Compat;
using VerdantsAscent.Managers;
using VerdantsAscent.Network;
using VerdantsAscent.Patches;
using VerdantsAscent.Sync;
using VerdantsAscent.Utilities;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("FiresAdminPrefabs")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("FiresAdminPrefabs")]
[assembly: AssemblyCopyright("Copyright ©  2025")]
[assembly: AssemblyTrademark("")]
[assembly: ComVisible(false)]
[assembly: Guid("62e9c40a-8cc2-4ba2-ba4a-1b41a307c62a")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: TargetFramework(".NETFramework,Version=v4.8", FrameworkDisplayName = ".NET Framework 4.8")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class IsUnmanagedAttribute : Attribute
	{
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace FiresAdminPrefabs.Patches
{
	[HarmonyPatch(typeof(Hud), "UpdatePieceBuildStatusAll")]
	public static class VAPieceBuildStatusGuard
	{
		private static FieldInfo s_knownRecipes;

		private static int s_lastSig = int.MinValue;

		public static bool Prefix(Player player)
		{
			if ((Object)(object)player == (Object)null)
			{
				return true;
			}
			int num = Signature(player);
			if (num == s_lastSig)
			{
				return false;
			}
			s_lastSig = num;
			return true;
		}

		private static int Signature(Player player)
		{
			int num = 17;
			if (s_knownRecipes == null)
			{
				s_knownRecipes = AccessTools.Field(typeof(Player), "m_knownRecipes");
			}
			if (s_knownRecipes != null && s_knownRecipes.GetValue(player) is HashSet<string> hashSet)
			{
				num = num * 31 + hashSet.Count;
			}
			Inventory inventory = ((Humanoid)player).GetInventory();
			if (inventory != null)
			{
				List<ItemData> allItems = inventory.GetAllItems();
				num = num * 31 + allItems.Count;
				for (int i = 0; i < allItems.Count; i++)
				{
					ItemData val = allItems[i];
					if (val != null)
					{
						if (val.m_shared != null && val.m_shared.m_name != null)
						{
							num = num * 31 + val.m_shared.m_name.GetHashCode();
						}
						num = num * 31 + val.m_stack;
					}
				}
			}
			return num;
		}
	}
}
namespace VerdantsAscent
{
	[BepInPlugin("com.Fire.adminprefabs", "FiresAdminPrefabs", "1.1.59")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class VaPieces : BaseUnityPlugin
	{
		[HarmonyPatch(typeof(Piece), "Awake")]
		public static class Piece_Awake_Patch
		{
			public static void Postfix(Piece __instance)
			{
				if (!((Object)(object)__instance == (Object)null))
				{
					DistantPieceRenderer.RegisterPiece(__instance);
				}
			}
		}

		[HarmonyPatch(typeof(WearNTear), "Destroy")]
		public static class WearNTear_Destroy_Patch
		{
			public static void Prefix(WearNTear __instance)
			{
				if (!((Object)(object)__instance == (Object)null))
				{
					ZNetView component = ((Component)__instance).GetComponent<ZNetView>();
					if (!((Object)(object)component == (Object)null) && component.IsValid())
					{
						DistantPieceRenderer.UnregisterPiece(component);
					}
				}
			}
		}

		[HarmonyPatch(typeof(ZNetScene), "Awake")]
		public static class ZNetScene_Awake_VAFapPieceShaderOverrider_Patch
		{
			[HarmonyPriority(0)]
			[HarmonyPostfix]
			public static void Postfix()
			{
				TryRun("VAFapPieceShaderOverrider.ApplyAllAtInit", VAFapPieceShaderOverrider.ApplyAllAtInit);
			}
		}

		[HarmonyPatch(typeof(ZNet), "OnDestroy")]
		public static class ZNet_OnDestroy_Patch
		{
			public static void Prefix()
			{
				DistantPieceRenderer.ClearAll();
			}
		}

		public static VaPieces Instance;

		public const string PluginGUID = "com.Fire.adminprefabs";

		public const string PluginName = "FiresAdminPrefabs";

		public const string PluginVersion = "1.1.59";

		public static ManualLogSource Log;

		public static readonly Harmony harmony = new Harmony("com.Fire.adminprefabs");

		private static readonly HashSet<string> _clientOnlyPatchTypes = new HashSet<string> { "VerdantsAscent.Patches.Player_UpdatePlacementGhost_HammerToggle_Patch" };

		private const float AdminPollFastIntervalSec = 2f;

		private const float AdminPollSlowIntervalSec = 10f;

		private const float AdminPollFastPhaseSec = 60f;

		private static bool _adminStatusHooked;

		public ConfigSync configSync;

		public static bool IsDedicatedServer { get; private set; }

		private void Awake()
		{
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0046: Invalid comparison between Unknown and I4
			//IL_007d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_008d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0098: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a8: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			FiresInputBlock.LogTag = "FAP";
			TryRun("VAFapBanner.PrintBig", VAFapBanner.PrintBig);
			IsDedicatedServer = (int)SystemInfo.graphicsDeviceType == 4;
			if (IsDedicatedServer)
			{
				Debug.Log((object)"[FiresAdminPrefabs] Detected DEDICATED SERVER context — UI patches will be skipped.");
			}
			VARuntimeMonoStub.LoadAll();
			VAExternalBundleLoader.BeginLoadAllBundlesAsync();
			VAFapPieceOverrides.Load();
			VAFapPieceShaderOverrider.Initialize();
			DistantPieceRenderer.Initialize();
			configSync = new ConfigSync("com.Fire.adminprefabs")
			{
				DisplayName = "FiresAdminPrefabs",
				CurrentVersion = "1.1.59",
				MinimumRequiredVersion = "1.1.59"
			};
			configSync.lockedConfigChanged += ConfigManager.Instance.OnConfigLockChanged;
			ConfigManager.Instance.Initialize(((BaseUnityPlugin)this).Config);
			BindConfigsToSync();
			TryRun("VAFapAssetSync.Init", VAFapAssetSync.Init);
			TryRun("ServerPushBridge register", RegisterServerPushBridge);
			if (Object.op_Implicit((Object)(object)ZNet.instance) && ZNet.instance.IsDedicated())
			{
				((MonoBehaviour)this).StartCoroutine(DelayedDedicatedServerInit());
			}
			else
			{
				FinishInit();
			}
		}

		private void Update()
		{
			VAFapThreadLog.Flush();
		}

		private static void RegisterServerPushBridge()
		{
			ServerPushBridge.RegisterPusher((Action<Action<string>>)delegate(Action<string> log)
			{
				VAFapAssetSync.PushPresetsNow(log);
			});
		}

		private IEnumerator DelayedDedicatedServerInit()
		{
			while ((Object)(object)ZNetScene.instance == (Object)null || (Object)(object)ObjectDB.instance == (Object)null)
			{
				yield return null;
			}
			yield return (object)new WaitForEndOfFrame();
			FinishInit();
		}

		private void FinishInit()
		{
			if (IsDedicatedServer)
			{
				PatchAllExceptClientOnlyTypes();
			}
			else
			{
				harmony.PatchAll();
			}
			if (!IsDedicatedServer)
			{
				TryRun("Help content registration", VAFapHelpContent.Register);
			}
			Debug.Log((object)string.Format("{0}: Init complete (dedicated: {1})", "FiresAdminPrefabs", IsDedicatedServer));
			((MonoBehaviour)this).StartCoroutine(WaitForZNetReady());
		}

		private static void PatchAllExceptClientOnlyTypes()
		{
			int num = 0;
			int num2 = 0;
			Type[] types = Assembly.GetExecutingAssembly().GetTypes();
			foreach (Type type in types)
			{
				if (IsClientOnlyPatch(type))
				{
					num2++;
					continue;
				}
				try
				{
					harmony.CreateClassProcessor(type).Patch();
					num++;
				}
				catch (Exception ex)
				{
					object[] customAttributes = type.GetCustomAttributes(typeof(HarmonyPatch), inherit: true);
					if (customAttributes != null && customAttributes.Length != 0)
					{
						Debug.LogWarning((object)("[FiresAdminPrefabs] Failed to apply Harmony patch " + type.FullName + ": " + ex.Message));
					}
				}
			}
			Debug.Log((object)string.Format("[{0}] Dedicated server: patched {1} types, skipped {2} client-only types.", "FiresAdminPrefabs", num, num2));
		}

		private static bool IsClientOnlyPatch(Type type)
		{
			Type type2 = type;
			while (type2 != null)
			{
				if (_clientOnlyPatchTypes.Contains(type2.FullName))
				{
					return true;
				}
				type2 = type2.DeclaringType;
			}
			return false;
		}

		private void BindConfigsToSync()
		{
			ConfigManager instance = ConfigManager.Instance;
			configSync.AddLockingConfigEntry<bool>(instance.configServerEnforced);
			configSync.AddConfigEntry<bool>(instance.configVerboseLogging);
			configSync.AddConfigEntry<bool>(instance.configHammerForceClipGround);
			configSync.AddConfigEntry<bool>(instance.configHammerForceClipEverything);
			configSync.AddConfigEntry<bool>(instance.configHammerDisableStaticPhysics);
			configSync.AddConfigEntry<bool>(instance.configHammerForcePersistence);
			configSync.AddConfigEntry<bool>(instance.configHammerSuppressRandomRotation);
			configSync.AddConfigEntry<bool>(instance.configHammerSwapFracOnPlace);
			configSync.AddConfigEntry<bool>(instance.configHammerEffectsOverrideTTL);
			configSync.AddConfigEntry<bool>(instance.configItemStandHideStandVisual);
			configSync.AddConfigEntry<bool>(instance.configAdminOnlyMode);
			configSync.AddConfigEntry<bool>(instance.configRegisterPiecesForNonAdmins);
			configSync.AddConfigEntry<bool>(instance.configWackyDatabaseCompat);
			configSync.AddConfigEntry<string>(instance.configVanillaFriendlyEffectAllowlist);
			configSync.AddConfigEntry<bool>(instance.configVanillaFriendly);
			configSync.AddConfigEntry<bool>(instance.configUseEwpForPersistence);
		}

		private void OnDestroy()
		{
			DistantPieceRenderer.ClearAll();
			ConfigManager.Instance.Dispose();
			if (configSync != null)
			{
				configSync.lockedConfigChanged -= ConfigManager.Instance.OnConfigLockChanged;
				configSync = null;
			}
			Instance = null;
		}

		private void OnConfigLockChanged()
		{
			ConfigManager.Instance?.OnConfigLockChanged();
		}

		private IEnumerator WaitForZNetReady()
		{
			if (VAFapLoadSummary.VerboseEnabled)
			{
				Debug.Log((object)"FiresAdminPrefabs: Waiting for ZNetScene...");
			}
			while ((Object)(object)ZNetScene.instance == (Object)null || ZNetScene.instance.m_prefabs == null || ZNetScene.instance.m_prefabs.Count == 0)
			{
				yield return null;
			}
			yield return (object)new WaitForEndOfFrame();
			VAVanillaPieceManager.InitializeOnce();
			VAFapEwpYamlWriter.EnsureSharedEntriesOnLoad();
			VAMonsterPresetStore.LoadAll();
			VAMonsterEwpRuleWriter.EnsureFileExists();
			VAMonsterPresetStore.EnableHotReload();
			VAChestPresetStore.LoadAll();
			VAChestEwpRuleWriter.EnsureFileExists();
			VAChestPresetStore.EnableHotReload();
			VAFapPersistentOptIn.LoadFromDisk();
			VAClutterPresetStore.LoadAll();
			VAClutterPresetStore.EnableHotReload();
			VAFapConsoleCommands.Register();
			VAClutterBrushIndicator.EnsureCreated();
			HookAdminPromotionWatchers();
			TryRun("VanillaNoise flush", ZLog_FilterFiresAdminPrefabs.ForceFlushIfDirty);
			TryRun("VAFapBanner.Print", VAFapBanner.Print);
		}

		private void HookAdminPromotionWatchers()
		{
			if (IsDedicatedServer)
			{
				return;
			}
			if (!_adminStatusHooked)
			{
				try
				{
					AdminSyncing.AdminStatusChanged += OnCoreAdminStatusChanged;
					_adminStatusHooked = true;
				}
				catch
				{
				}
			}
			if (ConfigManager.Instance.configAdminOnlyMode.Value && !VAVanillaPieceManager.IsAdminGateOpen())
			{
				((MonoBehaviour)this).StartCoroutine(PollForAdminPromotion());
			}
		}

		private static void OnCoreAdminStatusChanged(bool isAdmin)
		{
			if (isAdmin && !VAVanillaPieceManager.IsInitialized && VAVanillaPieceManager.IsAdminGateOpen())
			{
				Debug.Log((object)"FiresAdminPrefabs: Core admin-status push granted admin — running deferred InitializeOnce.");
				VAVanillaPieceManager.InitializeOnce();
				RefreshHammerTabsAndPieces();
			}
		}

		private IEnumerator PollForAdminPromotion()
		{
			float elapsed = 0f;
			bool loggedStillClosed = false;
			while (true)
			{
				float interval = ((elapsed < 60f) ? 2f : 10f);
				yield return (object)new WaitForSeconds(interval);
				elapsed += interval;
				if ((Object)(object)ZNet.instance == (Object)null)
				{
					break;
				}
				if (VAVanillaPieceManager.IsAdminGateOpen())
				{
					Debug.Log((object)string.Format("{0}: Admin gate opened after {1:F0}s of polling — running deferred InitializeOnce.", "FiresAdminPrefabs", elapsed));
					VAVanillaPieceManager.InitializeOnce();
					RefreshHammerTabsAndPieces();
					if (VAVanillaPieceManager.IsInitialized)
					{
						break;
					}
				}
				else if (!loggedStillClosed && elapsed >= 60f)
				{
					loggedStillClosed = true;
					if (ConfigManager.Instance.configVerboseLogging.Value)
					{
						Debug.Log((object)string.Format("{0}: Admin gate still closed after {1:F0}s — local player is not admin yet. Vanilla hammer remains active; still watching for a late promotion.", "FiresAdminPrefabs", 60f));
					}
				}
			}
		}

		private static void RefreshHammerTabsAndPieces()
		{
			if ((Object)(object)Player.m_localPlayer != (Object)null)
			{
				Player.m_localPlayer.UpdateAvailablePiecesList();
			}
		}

		private static void TryRun(string description, Action action)
		{
			try
			{
				action();
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[FiresAdminPrefabs] " + description + " failed: " + ex.Message));
			}
		}
	}
}
namespace VerdantsAscent.Network
{
	public static class VAFapConsoleCommands
	{
		[Serializable]
		[CompilerGenerated]
		private sealed class <>c
		{
			public static readonly <>c <>9 = new <>c();

			public static ConsoleEvent <>9__1_0;

			public static ConsoleEvent <>9__1_1;

			public static ConsoleEvent <>9__1_2;

			public static ConsoleEvent <>9__1_3;

			public static ConsoleEvent <>9__1_4;

			public static ConsoleEvent <>9__1_5;

			public static ConsoleEvent <>9__1_6;

			public static ConsoleEvent <>9__1_7;

			public static ConsoleEvent <>9__1_8;

			public static ConsoleEvent <>9__1_9;

			public static ConsoleEvent <>9__1_10;

			public static ConsoleEvent <>9__1_11;

			public static ConsoleEvent <>9__1_12;

			public static ConsoleEvent <>9__1_13;

			public static ConsoleEvent <>9__1_14;

			public static ConsoleEvent <>9__1_15;

			public static ConsoleEvent <>9__1_16;

			internal void <Register>b__1_0(ConsoleEventArgs args)
			{
				var (path, path2) = VAFapEwpYamlWriter.GetFilePaths();
				args.Context.AddString("[FAP] EWP yaml status:");
				args.Context.AddString(DescribeFile("scripts", path));
				args.Context.AddString(DescribeFile("data   ", path2));
				args.Context.AddString("[FAP] VanillaFriendly=" + ConfigManager.Instance.configVanillaFriendly.Value + " UseEwpForPersistence=" + ConfigManager.Instance.configUseEwpForPersistence.Value);
			}

			internal void <Register>b__1_1(ConsoleEventArgs args)
			{
				var (num, num2, num3, num4) = VAFapEwpYamlWriter.CleanStaleEntries();
				args.Context.AddString("[FAP] Clean complete:");
				args.Context.AddString($"  scripts: kept {num}, dropped {num2}");
				args.Context.AddString($"  data:    kept {num3}, dropped {num4}");
				if (num2 + num4 == 0)
				{
					args.Context.AddString("[FAP] No stale entries found — files are already clean.");
				}
			}

			internal void <Register>b__1_2(ConsoleEventArgs args)
			{
				if (args.Length < 2 || !string.Equals(args[1], "confirm", StringComparison.OrdinalIgnoreCase))
				{
					var (text, text2) = VAFapEwpYamlWriter.GetFilePaths();
					args.Context.AddString("[FAP] Wipe NOT performed (missing 'confirm' arg).");
					args.Context.AddString("[FAP] Files that WOULD be deleted:");
					args.Context.AddString("  " + text);
					args.Context.AddString("  " + text2);
					args.Context.AddString("[FAP] Re-run as: fap_wipeewp confirm");
				}
				else
				{
					bool flag = VAFapEwpYamlWriter.WipeLocalFiles();
					args.Context.AddString(flag ? "[FAP] FAP yaml files deleted + session guard reset. Rewrite on next world load." : "[FAP] Nothing to wipe — no FAP yaml files exist.");
				}
			}

			internal void <Register>b__1_3(ConsoleEventArgs args)
			{
				VAFapAssetSync.PushPresetsNow(new <>c__DisplayClass1_0
				{
					args = args
				}.<Register>b__17);
			}

			internal void <Register>b__1_4(ConsoleEventArgs args)
			{
				<>c__DisplayClass1_1 CS$<>8__locals3 = new <>c__DisplayClass1_1
				{
					args = args
				};
				string nameFilter = ((CS$<>8__locals3.args.Length > 1) ? CS$<>8__locals3.args[1] : null);
				VAFapAssetSync.PushBakedBundlesNow(delegate(string s)
				{
					CS$<>8__locals3.args.Context.AddString(s);
				}, nameFilter);
			}

			internal void <Register>b__1_5(ConsoleEventArgs args)
			{
				//IL_009a: Unknown result type (might be due to invalid IL or missing references)
				<>c__DisplayClass1_2 CS$<>8__locals5 = new <>c__DisplayClass1_2
				{
					args = args
				};
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					CS$<>8__locals5.args.Context.AddString("[FAP] No local player — load into a world first.");
					return;
				}
				bool destroy = false;
				float radius = 64f;
				for (int i = 1; i < CS$<>8__locals5.args.Length; i++)
				{
					float result;
					if (string.Equals(CS$<>8__locals5.args[i], "confirm", StringComparison.OrdinalIgnoreCase))
					{
						destroy = true;
					}
					else if (float.TryParse(CS$<>8__locals5.args[i], out result) && result > 0f)
					{
						radius = result;
					}
				}
				VAFapOrphanFxCleaner.Run(((Component)localPlayer).transform.position, radius, destroy, delegate(string s)
				{
					CS$<>8__locals5.args.Context.AddString(s);
				});
			}

			internal void <Register>b__1_6(ConsoleEventArgs args)
			{
				//IL_0075: Unknown result type (might be due to invalid IL or missing references)
				<>c__DisplayClass1_3 CS$<>8__locals4 = new <>c__DisplayClass1_3
				{
					args = args
				};
				Player localPlayer = Player.m_localPlayer;
				if ((Object)(object)localPlayer == (Object)null)
				{
					CS$<>8__locals4.args.Context.AddString("[FAP] No local player — load into a world first.");
					return;
				}
				float radius = 10f;
				for (int i = 1; i < CS$<>8__locals4.args.Length; i++)
				{
					if (float.TryParse(CS$<>8__locals4.args[i], out var result) && result > 0f)
					{
						radius = result;
					}
				}
				VAFapOrphanFxCleaner.Dump(((Component)localPlayer).transform.position, radius, delegate(string s)
				{
					CS$<>8__locals4.args.Context.AddString(s);
				});
			}

			internal void <Register>b__1_7(ConsoleEventArgs args)
			{
				if (ZDOMan.instance == null)
				{
					args.Context.AddString("[FAP] ZDOMan not ready — load into a world first.");
					return;
				}
				int stableHashCode = StringExtensionMethods.GetStableHashCode("FAP_persist");
				int num = 0;
				int num2 = 0;
				List<ZDO> list = VAZdoManAccess.SnapshotAllZdos();
				if (list == null)
				{
					args.Context.AddString("[FAP] Could not read ZDOMan's ZDO table — see the log.");
					return;
				}
				foreach (ZDO item in list)
				{
					if (item != null)
					{
						num2++;
						if (item.GetInt(stableHashCode, 0) != 0)
						{
							item.Set(stableHashCode, 0, false);
							item.IncreaseDataRevision();
							num++;
						}
					}
				}
				VAFapPersistentOptIn.Clear();
				args.Context.AddString($"[FAP] Scanned {num2} ZDO(s); cleared FAP_persist on {num}. " + "Session opt-ins reset. New placements will default to NOT persistent until you right-click an FX/VFX hammer piece and choose 'Place with Persistence'.");
			}

			internal void <Register>b__1_8(ConsoleEventArgs args)
			{
				List<VAFapOrphanZdoScan.OrphanGroup> list = VAFapOrphanZdoScan.Scan();
				if (list == null)
				{
					args.Context.AddString("[FAP] ZDO table not readable — load into a world first (see the log if this persists).");
					return;
				}
				if (list.Count == 0)
				{
					args.Context.AddString("[FAP] No orphaned ZDOs: every prefab referenced by the ZDOs this machine holds resolves.");
					return;
				}
				if (args.Length < 2 || !string.Equals(args[1], "purge", StringComparison.OrdinalIgnoreCase))
				{
					ReportOrphanGroups(args, list);
					return;
				}
				if (!IsLocalAdminForWorldEdit())
				{
					args.Context.AddString("[FAP] Purge refused — admin only.");
					return;
				}
				string target = ((args.Length >= 3) ? args[2] : null);
				List<int> list2 = SelectOrphanHashes(args, list, target);
				if (list2 != null)
				{
					int num = VAFapOrphanZdoScan.Purge(list2);
					args.Context.AddString($"[FAP] Destroyed {num} orphaned ZDO(s) across {list2.Count} prefab(s). " + "Re-run the command to confirm the world is clean.");
				}
			}

			internal void <Register>b__1_9(ConsoleEventArgs args)
			{
				string assetsDir = VAExternalBundleLoader.AssetsDir;
				args.Context.AddString("[FAP] Bundle assets dir: " + assetsDir);
				if (!Directory.Exists(assetsDir))
				{
					args.Context.AddString("[FAP] Folder DOES NOT EXIST — Awake-time creation failed (check BepInEx log for warning).");
					return;
				}
				string[] files;
				try
				{
					files = Directory.GetFiles(assetsDir);
				}
				catch (Exception ex)
				{
					args.Context.AddString("[FAP] Could not list folder: " + ex.Message);
					return;
				}
				if (files.Length == 0)
				{
					args.Context.AddString("[FAP] Folder is empty. Drop bundles here, then reload the world.");
					return;
				}
				args.Context.AddString($"[FAP] Contents ({files.Length} files):");
				string[] array = files;
				for (int i = 0; i < array.Length; i++)
				{
					FileInfo fileInfo = new FileInfo(array[i]);
					args.Context.AddString($"  {fileInfo.Name} ({fileInfo.Length} bytes)");
				}
			}

			internal void <Register>b__1_10(ConsoleEventArgs args)
			{
				bool force = args.Length > 1 && args[1].Equals("force", StringComparison.OrdinalIgnoreCase);
				ImportFromYoink(args, force);
			}

			internal void <Register>b__1_11(ConsoleEventArgs args)
			{
				(int, int) tuple = VAPieceIconRenderer.ReloadAll();
				args.Context.AddString($"[FAP] fap_reload_icons: deleted {tuple.Item1} cached PNG(s), re-queued {tuple.Item2} piece(s) for re-render. Icons will refresh as the queue drains (one per frame).");
			}

			internal void <Register>b__1_12(ConsoleEventArgs args)
			{
				if ((Object)(object)VaPieces.Instance == (Object)null)
				{
					args.Context.AddString("[FAP] fap_rebake_all: plugin instance not available — try again after world is fully loaded.");
					return;
				}
				args.Context.AddString("[FAP] fap_rebake_all: re-baking every registered blueprint into fresh bundles. Watch the message hud + log for progress; this can take minutes per megablueprint.");
				((MonoBehaviour)VaPieces.Instance).StartCoroutine(VAHammerRightClickPatches.RebakeAllCoroutine());
			}

			internal void <Register>b__1_13(ConsoleEventArgs args)
			{
				bool num = args.Length >= 2 && string.Equals(args[1], "apply", StringComparison.OrdinalIgnoreCase);
				var (num2, num3, num4) = VAHammerTabOrganizer.RecategorizeClutter(!num);
				if (!num)
				{
					args.Context.AddString("[FAP] fap_recategorize_clutter DRY-RUN:");
					args.Context.AddString($"  Would migrate {num2} prefab(s) from {num3} folder(s) into Clutter/prefabs.txt.");
					if (num4 > 0)
					{
						args.Context.AddString($"  {num4} disk entry/entries skipped — no GameObject currently loaded for those names (probably from a removed mod).");
					}
					if (num2 > 0)
					{
						args.Context.AddString("[FAP] Re-run as 'fap_recategorize_clutter apply' to commit.");
					}
					else
					{
						args.Context.AddString("[FAP] Nothing to migrate — every clutter prefab is already in Clutter/.");
					}
					return;
				}
				args.Context.AddString("[FAP] fap_recategorize_clutter APPLY:");
				args.Context.AddString($"  Migrated {num2} prefab(s) from {num3} folder(s) into Clutter/prefabs.txt.");
				if (num4 > 0)
				{
					args.Context.AddString($"  {num4} disk entry/entries skipped (no live GameObject).");
				}
				if (num2 > 0)
				{
					VAVanillaPieceManager.RecategorizeEligiblePieces();
					args.Context.AddString("[FAP] Re-categorized live pieces. Open the hammer to see the Clutter tab refreshed.");
				}
			}

			internal void <Register>b__1_14(ConsoleEventArgs args)
			{
				//IL_021b: Unknown result type (might be due to invalid IL or missing references)
				bool flag = args.Length >= 2 && string.Equals(args[1], "apply", StringComparison.OrdinalIgnoreCase);
				ZNetScene instance = ZNetScene.instance;
				if ((Object)(object)instance == (Object)null)
				{
					args.Context.AddString("[FAP] fap_clean_stale_clutter: ZNetScene not ready — try after fully spawning in.");
					return;
				}
				string[] array = new string[15]
				{
					"D_sfx_rock_destroy", "D_sfx_", "instanced_small_rock1", "instanced_forest_groundcover", "instanced_", "FAP_PaintedClutter_", "_VFX_", "_vfx_", "VFX_", "_SFX_",
					"_sfx_", "SFX_", "_FX_", "_fx_", "Krump_Mat_"
				};
				List<GameObject> list = new List<GameObject>();
				foreach (KeyValuePair<ZDO, ZNetView> instance2 in instance.m_instances)
				{
					ZNetView value = instance2.Value;
					if ((Object)(object)value == (Object)null)
					{
						continue;
					}
					GameObject gameObject;
					try
					{
						gameObject = ((Component)value).gameObject;
					}
					catch
					{
						continue;
					}
					if ((Object)(object)gameObject == (Object)null)
					{
						continue;
					}
					string text;
					try
					{
						text = ((Object)gameObject).name ?? string.Empty;
					}
					catch
					{
						continue;
					}
					if (text.Length == 0)
					{
						continue;
					}
					bool flag2 = false;
					for (int i = 0; i < array.Length; i++)
					{
						if (text.IndexOf(array[i], StringComparison.Ordinal) >= 0)
						{
							flag2 = true;
							break;
						}
					}
					if (flag2)
					{
						list.Add(gameObject);
					}
				}
				if (!flag)
				{
					args.Context.AddString("[FAP] fap_clean_stale_clutter DRY-RUN:");
					args.Context.AddString($"  Found {list.Count} stale clutter ZDO(s) in ZNetScene.m_instances.");
					if (list.Count > 0)
					{
						int num = Math.Min(list.Count, 10);
						args.Context.AddString($"  First {num} match(es):");
						for (int j = 0; j < num; j++)
						{
							args.Context.AddString($"    - {((Object)list[j]).name} @ {list[j].transform.position}");
						}
						args.Context.AddString("[FAP] Re-run as 'fap_clean_stale_clutter apply' to destroy them.");
					}
					else
					{
						args.Context.AddString("[FAP] Nothing to clean — world save is free of stale clutter ZDOs.");
					}
					return;
				}
				int num2 = 0;
				foreach (GameObject item in list)
				{
					try
					{
						NetworkObjectHelper.SafeDestroy(item);
						num2++;
					}
					catch (Exception ex)
					{
						Debug.LogWarning((object)("FiresAdminPrefabs: fap_clean_stale_clutter — SafeDestroy threw on '" + ((item != null) ? ((Object)item).name : null) + "': " + ex.Message));
					}
				}
				args.Context.AddString($"[FAP] fap_clean_stale_clutter APPLY: destroyed {num2} stale clutter ZDO(s) via SafeDestroy. World save will reflect the cleanup on next save tick (or zone unload). Re-run after a /save command to persist the cleanup.");
			}

			internal void <Register>b__1_15(ConsoleEventArgs args)
			{
				//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
				<>c__DisplayClass1_4 CS$<>8__locals13 = new <>c__DisplayClass1_4
				{
					args = args
				};
				if (CS$<>8__locals13.args.Length < 2)
				{
					CS$<>8__locals13.args.Context.AddString("[FAP] usage: fap_bake_diagnose <blueprintName> [nameFilter]");
					return;
				}
				ZNetScene instance = ZNetScene.instance;
				if ((Object)(object)instance == (Object)null)
				{
					CS$<>8__locals13.args.Context.AddString("[FAP] fap_bake_diagnose: ZNetScene not ready — run after fully spawning in.");
					return;
				}
				string text = CS$<>8__locals13.args[1];
				string nameFilter = ((CS$<>8__locals13.args.Length >= 3) ? CS$<>8__locals13.args[2] : null);
				VABlueprintLoader.Blueprint blueprint = VABlueprintLoader.LoadByName(text);
				if (blueprint == null)
				{
					CS$<>8__locals13.args.Context.AddString("[FAP] fap_bake_diagnose: blueprint '" + text + "' not found in any probed blueprint folder.");
					return;
				}
				blueprint.Center(string.Empty);
				CS$<>8__locals13.lineCount = 0;
				Action<string> emit = delegate(string line)
				{
					CS$<>8__locals13.args.Context.AddString(line);
					Debug.Log((object)("FiresAdminPrefabs: [bake_diagnose] " + line));
					CS$<>8__locals13.lineCount++;
				};
				try
				{
					VAFapBlueprintMeshBaker.DiagnoseBlueprintBake(blueprint, instance, nameFilter, emit);
				}
				catch (Exception ex)
				{
					CS$<>8__locals13.args.Context.AddString("[FAP] fap_bake_diagnose threw: " + ex.Message);
					Debug.LogWarning((object)string.Format("{0}: fap_bake_diagnose threw: {1}", "FiresAdminPrefabs", ex));
					return;
				}
				CS$<>8__locals13.args.Context.AddString($"[FAP] fap_bake_diagnose: {CS$<>8__locals13.lineCount} line(s) — full detail in LogOutput.log (search '[bake_diagnose]').");
			}

			internal void <Register>b__1_16(ConsoleEventArgs args)
			{
				if ((Object)(object)VaPieces.Instance == (Object)null)
				{
					args.Context.AddString("[FAP] fap_bake_all: plugin instance not available — try again after the world is fully loaded.");
					return;
				}
				args.Context.AddString("[FAP] fap_bake_all: baking ALL blueprints + writing the diagnostic report. Watch the message hud; this can take many minutes. Report → config/FiresAdminPrefabs/Diagnostics/bake_all_report.txt");
				((MonoBehaviour)VaPieces.Instance).StartCoroutine(VAHammerRightClickPatches.BakeAllWithDiagnosticCoroutine());
			}
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass1_0
		{
			public ConsoleEventArgs args;

			internal void <Register>b__17(string s)
			{
				args.Context.AddString(s);
			}
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass1_1
		{
			public ConsoleEventArgs args;

			internal void <Register>b__18(string s)
			{
				args.Context.AddString(s);
			}
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass1_2
		{
			public ConsoleEventArgs args;

			internal void <Register>b__19(string s)
			{
				args.Context.AddString(s);
			}
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass1_3
		{
			public ConsoleEventArgs args;

			internal void <Register>b__20(string s)
			{
				args.Context.AddString(s);
			}
		}

		[CompilerGenerated]
		private sealed class <>c__DisplayClass1_4
		{
			public ConsoleEventArgs args;

			public int lineCount;

			internal void <Register>b__21(string line)
			{
				args.Context.AddString(line);
				Debug.Log((object)("FiresAdminPrefabs: [bake_diagnose] " + line));
				lineCount++;
			}
		}

		private static bool _registered;

		private const string CommandNamePrefix = "fap_";

		public static void Register()
		{
			//IL_0049: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_0039: Unknown result type (might be due to invalid IL or missing references)
			//IL_003f: Expected O, but got Unknown
			//IL_0082: Unknown result type (might be due to invalid IL or missing references)
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0072: Unknown result type (might be due to invalid IL or missing references)
			//IL_0078: Expected O, but got Unknown
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Expected O, but got Unknown
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ea: Expected O, but got Unknown
			//IL_012d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0118: Unknown result type (might be due to invalid IL or missing references)
			//IL_011d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Expected O, but got Unknown
			//IL_0166: Unknown result type (might be due to invalid IL or missing references)
			//IL_0151: Unknown result type (might be due to invalid IL or missing references)
			//IL_0156: Unknown result type (might be due to invalid IL or missing references)
			//IL_015c: Expected O, but got Unknown
			//IL_019f: Unknown result type (might be due to invalid IL or missing references)
			//IL_018a: Unknown result type (might be due to invalid IL or missing references)
			//IL_018f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0195: Expected O, but got Unknown
			//IL_01d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_01ce: Expected O, but got Unknown
			//IL_0211: Unknown result type (might be due to invalid IL or missing references)
			//IL_01fc: Unknown result type (might be due to invalid IL or missing references)
			//IL_0201: Unknown result type (might be due to invalid IL or missing references)
			//IL_0207: Expected O, but got Unknown
			//IL_024a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0235: Unknown result type (might be due to invalid IL or missing references)
			//IL_023a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0240: Expected O, but got Unknown
			//IL_0283: Unknown result type (might be due to invalid IL or missing references)
			//IL_026e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0273: Unknown result type (might be due to invalid IL or missing references)
			//IL_0279: Expected O, but got Unknown
			//IL_02bc: Unknown result type (might be due to invalid IL or missing references)
			//IL_02a7: Unknown result type (might be due to invalid IL or missing references)
			//IL_02ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_02b2: Expected O, but got Unknown
			//IL_02f5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e0: Unknown result type (might be due to invalid IL or missing references)
			//IL_02e5: Unknown result type (might be due to invalid IL or missing references)
			//IL_02eb: Expected O, but got Unknown
			//IL_032e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0319: Unknown result type (might be due to invalid IL or missing references)
			//IL_031e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0324: Expected O, but got Unknown
			//IL_0367: Unknown result type (might be due to invalid IL or missing references)
			//IL_0352: Unknown result type (might be due to invalid IL or missing references)
			//IL_0357: Unknown result type (might be due to invalid IL or missing references)
			//IL_035d: Expected O, but got Unknown
			//IL_03a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_038b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0390: Unknown result type (might be due to invalid IL or missing references)
			//IL_0396: Expected O, but got Unknown
			//IL_03d9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c4: Unknown result type (might be due to invalid IL or missing references)
			//IL_03c9: Unknown result type (might be due to invalid IL or missing references)
			//IL_03cf: Expected O, but got Unknown
			if (_registered)
			{
				return;
			}
			_registered = true;
			if (VaPieces.IsDedicatedServer)
			{
				return;
			}
			object obj = <>c.<>9__1_0;
			if (obj == null)
			{
				ConsoleEvent val = delegate(ConsoleEventArgs args)
				{
					var (path, path2) = VAFapEwpYamlWriter.GetFilePaths();
					args.Context.AddString("[FAP] EWP yaml status:");
					args.Context.AddString(DescribeFile("scripts", path));
					args.Context.AddString(DescribeFile("data   ", path2));
					args.Context.AddString("[FAP] VanillaFriendly=" + ConfigManager.Instance.configVanillaFriendly.Value + " UseEwpForPersistence=" + ConfigManager.Instance.configUseEwpForPersistence.Value);
				};
				<>c.<>9__1_0 = val;
				obj = (object)val;
			}
			new ConsoleCommand("fap_ewpstatus", "Show the FAP yaml file paths + their current sizes.", (ConsoleEvent)obj, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj2 = <>c.<>9__1_1;
			if (obj2 == null)
			{
				ConsoleEvent val2 = delegate(ConsoleEventArgs args)
				{
					var (num, num2, num3, num4) = VAFapEwpYamlWriter.CleanStaleEntries();
					args.Context.AddString("[FAP] Clean complete:");
					args.Context.AddString($"  scripts: kept {num}, dropped {num2}");
					args.Context.AddString($"  data:    kept {num3}, dropped {num4}");
					if (num2 + num4 == 0)
					{
						args.Context.AddString("[FAP] No stale entries found — files are already clean.");
					}
				};
				<>c.<>9__1_1 = val2;
				obj2 = (object)val2;
			}
			new ConsoleCommand("fap_cleanewp", "Migration sweep: drops any per-prefab entries left over from older FAP versions and ensures only the single universal rule remains. Local-only.", (ConsoleEvent)obj2, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj3 = <>c.<>9__1_2;
			if (obj3 == null)
			{
				ConsoleEvent val3 = delegate(ConsoleEventArgs args)
				{
					if (args.Length < 2 || !string.Equals(args[1], "confirm", StringComparison.OrdinalIgnoreCase))
					{
						var (text, text2) = VAFapEwpYamlWriter.GetFilePaths();
						args.Context.AddString("[FAP] Wipe NOT performed (missing 'confirm' arg).");
						args.Context.AddString("[FAP] Files that WOULD be deleted:");
						args.Context.AddString("  " + text);
						args.Context.AddString("  " + text2);
						args.Context.AddString("[FAP] Re-run as: fap_wipeewp confirm");
					}
					else
					{
						bool flag = VAFapEwpYamlWriter.WipeLocalFiles();
						args.Context.AddString(flag ? "[FAP] FAP yaml files deleted + session guard reset. Rewrite on next world load." : "[FAP] Nothing to wipe — no FAP yaml files exist.");
					}
				};
				<>c.<>9__1_2 = val3;
				obj3 = (object)val3;
			}
			new ConsoleCommand("fap_wipeewp", "Delete the FAP yaml files and reset the session guard so they're rewritten on next world load. Type 'fap_wipeewp confirm' to actually delete (no confirm arg = no-op, prevents fat-finger wipes).", (ConsoleEvent)obj3, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj4 = <>c.<>9__1_3;
			if (obj4 == null)
			{
				ConsoleEvent val4 = delegate(ConsoleEventArgs args)
				{
					VAFapAssetSync.PushPresetsNow(delegate(string s)
					{
						args.Context.AddString(s);
					});
				};
				<>c.<>9__1_3 = val4;
				obj4 = (object)val4;
			}
			new ConsoleCommand("fap_push_presets", "Push your local FAP chest/monster preset yaml + EWP rule yaml UP to the connected server so respawns fire server-side. Admin-only; only works when connected to a REMOTE server (on a listen-host the files are already local). Same payload as the AutoPushOnPlace feature, on demand. The server admin-gates the write and reloads its presets so rules go live without a restart.", (ConsoleEvent)obj4, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj5 = <>c.<>9__1_4;
			if (obj5 == null)
			{
				ConsoleEvent val5 = delegate(ConsoleEventArgs args)
				{
					string nameFilter = ((args.Length > 1) ? args[1] : null);
					VAFapAssetSync.PushBakedBundlesNow(delegate(string s)
					{
						args.Context.AddString(s);
					}, nameFilter);
				};
				<>c.<>9__1_4 = val5;
				obj5 = (object)val5;
			}
			new ConsoleCommand("fap_push_bundle", "Push your locally baked blueprint pieces UP to the connected server — each Assets/*.bakebundle plus its Manifests/*.bakemanifest sidecar. No arg pushes all of them; pass part of a name to push just the matching ones. Admin-only, remote servers only. The server stores the files, hands them to every other client on connect, and registers the piece at its next world load. Baked bundles run to tens of MB, so expect the transfer to take a moment.", (ConsoleEvent)obj5, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj6 = <>c.<>9__1_5;
			if (obj6 == null)
			{
				ConsoleEvent val6 = delegate(ConsoleEventArgs args)
				{
					//IL_009a: Unknown result type (might be due to invalid IL or missing references)
					Player localPlayer = Player.m_localPlayer;
					if ((Object)(object)localPlayer == (Object)null)
					{
						args.Context.AddString("[FAP] No local player — load into a world first.");
					}
					else
					{
						bool destroy = false;
						float radius = 64f;
						for (int i = 1; i < args.Length; i++)
						{
							float result;
							if (string.Equals(args[i], "confirm", StringComparison.OrdinalIgnoreCase))
							{
								destroy = true;
							}
							else if (float.TryParse(args[i], out result) && result > 0f)
							{
								radius = result;
							}
						}
						VAFapOrphanFxCleaner.Run(((Component)localPlayer).transform.position, radius, destroy, delegate(string s)
						{
							args.Context.AddString(s);
						});
					}
				};
				<>c.<>9__1_5 = val6;
				obj6 = (object)val6;
			}
			new ConsoleCommand("fap_clear_orphan_fx", "Sweep orphaned vanilla status-effect FX (wet / frost / etc.) left in the world when their character unloaded — the non-networked follow-FX the normal FX clear brush can't see. No arg or 'scan [radius]' reports; 'confirm [radius]' destroys. Radius defaults to 64m, centered on you. Args are order-independent.", (ConsoleEvent)obj6, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj7 = <>c.<>9__1_6;
			if (obj7 == null)
			{
				ConsoleEvent val7 = delegate(ConsoleEventArgs args)
				{
					//IL_0075: Unknown result type (might be due to invalid IL or missing references)
					Player localPlayer = Player.m_localPlayer;
					if ((Object)(object)localPlayer == (Object)null)
					{
						args.Context.AddString("[FAP] No local player — load into a world first.");
					}
					else
					{
						float radius = 10f;
						for (int i = 1; i < args.Length; i++)
						{
							if (float.TryParse(args[i], out var result) && result > 0f)
							{
								radius = result;
							}
						}
						VAFapOrphanFxCleaner.Dump(((Component)localPlayer).transform.position, radius, delegate(string s)
						{
							args.Context.AddString(s);
						});
					}
				};
				<>c.<>9__1_6 = val7;
				obj7 = (object)val7;
			}
			new ConsoleCommand("fap_fx_dump", "Diagnostic: list EVERY renderer-bearing object near you (NO filtering) with its renderer type + lineage (root name, ZNetView/Character ancestor, ParentConstraint state, layer), closest first. Use to identify an FX that fap_clear_orphan_fx isn't catching. Optional radius (default 10m).", (ConsoleEvent)obj7, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj8 = <>c.<>9__1_7;
			if (obj8 == null)
			{
				ConsoleEvent val8 = delegate(ConsoleEventArgs args)
				{
					if (ZDOMan.instance == null)
					{
						args.Context.AddString("[FAP] ZDOMan not ready — load into a world first.");
					}
					else
					{
						int stableHashCode = StringExtensionMethods.GetStableHashCode("FAP_persist");
						int num = 0;
						int num2 = 0;
						List<ZDO> list = VAZdoManAccess.SnapshotAllZdos();
						if (list == null)
						{
							args.Context.AddString("[FAP] Could not read ZDOMan's ZDO table — see the log.");
						}
						else
						{
							foreach (ZDO item in list)
							{
								if (item != null)
								{
									num2++;
									if (item.GetInt(stableHashCode, 0) != 0)
									{
										item.Set(stableHashCode, 0, false);
										item.IncreaseDataRevision();
										num++;
									}
								}
							}
							VAFapPersistentOptIn.Clear();
							args.Context.AddString($"[FAP] Scanned {num2} ZDO(s); cleared FAP_persist on {num}. " + "Session opt-ins reset. New placements will default to NOT persistent until you right-click an FX/VFX hammer piece and choose 'Place with Persistence'.");
						}
					}
				};
				<>c.<>9__1_7 = val8;
				obj8 = (object)val8;
			}
			new ConsoleCommand("fap_clear_legacy_persistence", "Strip FAP_persist=1 from EVERY ZDO in the world. Use this once after updating to recover from the pre-fix era where FX attached to monsters / vanilla content got tagged for respawn. After running this, only pieces you explicitly opt-in via right-click → 'Place with Persistence' will be persistent going forward.", (ConsoleEvent)obj8, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj9 = <>c.<>9__1_8;
			if (obj9 == null)
			{
				ConsoleEvent val9 = delegate(ConsoleEventArgs args)
				{
					List<VAFapOrphanZdoScan.OrphanGroup> list = VAFapOrphanZdoScan.Scan();
					if (list == null)
					{
						args.Context.AddString("[FAP] ZDO table not readable — load into a world first (see the log if this persists).");
					}
					else if (list.Count == 0)
					{
						args.Context.AddString("[FAP] No orphaned ZDOs: every prefab referenced by the ZDOs this machine holds resolves.");
					}
					else if (args.Length < 2 || !string.Equals(args[1], "purge", StringComparison.OrdinalIgnoreCase))
					{
						ReportOrphanGroups(args, list);
					}
					else if (!IsLocalAdminForWorldEdit())
					{
						args.Context.AddString("[FAP] Purge refused — admin only.");
					}
					else
					{
						string target = ((args.Length >= 3) ? args[2] : null);
						List<int> list2 = SelectOrphanHashes(args, list, target);
						if (list2 != null)
						{
							int num = VAFapOrphanZdoScan.Purge(list2);
							args.Context.AddString($"[FAP] Destroyed {num} orphaned ZDO(s) across {list2.Count} prefab(s). " + "Re-run the command to confirm the world is clean.");
						}
					}
				};
				<>c.<>9__1_8 = val9;
				obj9 = (object)val9;
			}
			new ConsoleCommand("fap_orphan_zdo", "Report ZDOs whose prefab no longer exists on this machine — the objects behind vanilla's 'Missing prefab hash' log spam — one line per prefab with a ZDO count and a sample position. FAP names its own content (blueprint stubs, baked bundles) from the blueprint files on disk; anything else is reported as unknown, because it usually belongs to a mod that is only temporarily absent. 'fap_orphan_zdo purge' deletes only the FAP content it could name. 'fap_orphan_zdo purge <hash>' deletes one prefab's ZDOs. 'fap_orphan_zdo purge all' deletes every orphan, other mods' included — only do that when their mod is gone for good. Purging is a world edit and is admin-only. Only ZDOs this machine currently holds are scanned, so fly the area first for a distant build.", (ConsoleEvent)obj9, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj10 = <>c.<>9__1_9;
			if (obj10 == null)
			{
				ConsoleEvent val10 = delegate(ConsoleEventArgs args)
				{
					string assetsDir = VAExternalBundleLoader.AssetsDir;
					args.Context.AddString("[FAP] Bundle assets dir: " + assetsDir);
					if (!Directory.Exists(assetsDir))
					{
						args.Context.AddString("[FAP] Folder DOES NOT EXIST — Awake-time creation failed (check BepInEx log for warning).");
					}
					else
					{
						string[] files;
						try
						{
							files = Directory.GetFiles(assetsDir);
						}
						catch (Exception ex)
						{
							args.Context.AddString("[FAP] Could not list folder: " + ex.Message);
							return;
						}
						if (files.Length == 0)
						{
							args.Context.AddString("[FAP] Folder is empty. Drop bundles here, then reload the world.");
						}
						else
						{
							args.Context.AddString($"[FAP] Contents ({files.Length} files):");
							string[] array = files;
							for (int i = 0; i < array.Length; i++)
							{
								FileInfo fileInfo = new FileInfo(array[i]);
								args.Context.AddString($"  {fileInfo.Name} ({fileInfo.Length} bytes)");
							}
						}
					}
				};
				<>c.<>9__1_9 = val10;
				obj10 = (object)val10;
			}
			new ConsoleCommand("fap_assetsdir", "Print the absolute path to the FAP bundle-assets folder + list its current contents. The folder is auto-created at plugin Awake; drop *.bundle / *.assetbundle / *.unity3d files (or extensionless UnityFS bundles ripped via SteamyDumps) here for FAP to load on next world entry.", (ConsoleEvent)obj10, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj11 = <>c.<>9__1_10;
			if (obj11 == null)
			{
				ConsoleEvent val11 = delegate(ConsoleEventArgs args)
				{
					bool force = args.Length > 1 && args[1].Equals("force", StringComparison.OrdinalIgnoreCase);
					ImportFromYoink(args, force);
				};
				<>c.<>9__1_10 = val11;
				obj11 = (object)val11;
			}
			new ConsoleCommand("fap_import_yoink", "Pull bundles + manifests from FiresSteamyDumps' YoinkScanMe output into FAP's Assets/ and Manifests/ folders. Usage: fap_import_yoink [force]", (ConsoleEvent)obj11, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj12 = <>c.<>9__1_11;
			if (obj12 == null)
			{
				ConsoleEvent val12 = delegate(ConsoleEventArgs args)
				{
					(int, int) tuple = VAPieceIconRenderer.ReloadAll();
					args.Context.AddString($"[FAP] fap_reload_icons: deleted {tuple.Item1} cached PNG(s), re-queued {tuple.Item2} piece(s) for re-render. Icons will refresh as the queue drains (one per frame).");
				};
				<>c.<>9__1_11 = val12;
				obj12 = (object)val12;
			}
			new ConsoleCommand("fap_reload_icons", "Clear the FAP icon cache and re-render hammer icons for every injected piece. New PNGs are written as the queue completes.", (ConsoleEvent)obj12, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj13 = <>c.<>9__1_12;
			if (obj13 == null)
			{
				ConsoleEvent val13 = delegate(ConsoleEventArgs args)
				{
					if ((Object)(object)VaPieces.Instance == (Object)null)
					{
						args.Context.AddString("[FAP] fap_rebake_all: plugin instance not available — try again after world is fully loaded.");
					}
					else
					{
						args.Context.AddString("[FAP] fap_rebake_all: re-baking every registered blueprint into fresh bundles. Watch the message hud + log for progress; this can take minutes per megablueprint.");
						((MonoBehaviour)VaPieces.Instance).StartCoroutine(VAHammerRightClickPatches.RebakeAllCoroutine());
					}
				};
				<>c.<>9__1_12 = val13;
				obj13 = (object)val13;
			}
			new ConsoleCommand("fap_rebake_all", "Bake every registered blueprint into a fresh bundle, replacing existing bakes. Runs one blueprint at a time; relaunch afterwards to use the new prefabs.", (ConsoleEvent)obj13, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj14 = <>c.<>9__1_13;
			if (obj14 == null)
			{
				ConsoleEvent val14 = delegate(ConsoleEventArgs args)
				{
					bool num = args.Length >= 2 && string.Equals(args[1], "apply", StringComparison.OrdinalIgnoreCase);
					var (num2, num3, num4) = VAHammerTabOrganizer.RecategorizeClutter(!num);
					if (!num)
					{
						args.Context.AddString("[FAP] fap_recategorize_clutter DRY-RUN:");
						args.Context.AddString($"  Would migrate {num2} prefab(s) from {num3} folder(s) into Clutter/prefabs.txt.");
						if (num4 > 0)
						{
							args.Context.AddString($"  {num4} disk entry/entries skipped — no GameObject currently loaded for those names (probably from a removed mod).");
						}
						if (num2 > 0)
						{
							args.Context.AddString("[FAP] Re-run as 'fap_recategorize_clutter apply' to commit.");
						}
						else
						{
							args.Context.AddString("[FAP] Nothing to migrate — every clutter prefab is already in Clutter/.");
						}
					}
					else
					{
						args.Context.AddString("[FAP] fap_recategorize_clutter APPLY:");
						args.Context.AddString($"  Migrated {num2} prefab(s) from {num3} folder(s) into Clutter/prefabs.txt.");
						if (num4 > 0)
						{
							args.Context.AddString($"  {num4} disk entry/entries skipped (no live GameObject).");
						}
						if (num2 > 0)
						{
							VAVanillaPieceManager.RecategorizeEligiblePieces();
							args.Context.AddString("[FAP] Re-categorized live pieces. Open the hammer to see the Clutter tab refreshed.");
						}
					}
				};
				<>c.<>9__1_13 = val14;
				obj14 = (object)val14;
			}
			new ConsoleCommand("fap_recategorize_clutter", "Walk every disk-override entry, identify prefabs that are TRULY clutter (InstanceRenderer / ClutterSystem membership / BundleClutterAllOf) but parked in a non-Clutter folder, and migrate them into Clutter/prefabs.txt. Usage: fap_recategorize_clutter [apply]. Without 'apply', dry-run only.", (ConsoleEvent)obj14, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj15 = <>c.<>9__1_14;
			if (obj15 == null)
			{
				ConsoleEvent val15 = delegate(ConsoleEventArgs args)
				{
					//IL_021b: Unknown result type (might be due to invalid IL or missing references)
					bool flag = args.Length >= 2 && string.Equals(args[1], "apply", StringComparison.OrdinalIgnoreCase);
					ZNetScene instance = ZNetScene.instance;
					if ((Object)(object)instance == (Object)null)
					{
						args.Context.AddString("[FAP] fap_clean_stale_clutter: ZNetScene not ready — try after fully spawning in.");
					}
					else
					{
						string[] array = new string[15]
						{
							"D_sfx_rock_destroy", "D_sfx_", "instanced_small_rock1", "instanced_forest_groundcover", "instanced_", "FAP_PaintedClutter_", "_VFX_", "_vfx_", "VFX_", "_SFX_",
							"_sfx_", "SFX_", "_FX_", "_fx_", "Krump_Mat_"
						};
						List<GameObject> list = new List<GameObject>();
						foreach (KeyValuePair<ZDO, ZNetView> instance2 in instance.m_instances)
						{
							ZNetView value = instance2.Value;
							if (!((Object)(object)value == (Object)null))
							{
								GameObject gameObject;
								try
								{
									gameObject = ((Component)value).gameObject;
								}
								catch
								{
									continue;
								}
								if (!((Object)(object)gameObject == (Object)null))
								{
									string text;
									try
									{
										text = ((Object)gameObject).name ?? string.Empty;
									}
									catch
									{
										continue;
									}
									if (text.Length != 0)
									{
										bool flag2 = false;
										for (int i = 0; i < array.Length; i++)
										{
											if (text.IndexOf(array[i], StringComparison.Ordinal) >= 0)
											{
												flag2 = true;
												break;
											}
										}
										if (flag2)
										{
											list.Add(gameObject);
										}
									}
								}
							}
						}
						if (!flag)
						{
							args.Context.AddString("[FAP] fap_clean_stale_clutter DRY-RUN:");
							args.Context.AddString($"  Found {list.Count} stale clutter ZDO(s) in ZNetScene.m_instances.");
							if (list.Count > 0)
							{
								int num = Math.Min(list.Count, 10);
								args.Context.AddString($"  First {num} match(es):");
								for (int j = 0; j < num; j++)
								{
									args.Context.AddString($"    - {((Object)list[j]).name} @ {list[j].transform.position}");
								}
								args.Context.AddString("[FAP] Re-run as 'fap_clean_stale_clutter apply' to destroy them.");
							}
							else
							{
								args.Context.AddString("[FAP] Nothing to clean — world save is free of stale clutter ZDOs.");
							}
						}
						else
						{
							int num2 = 0;
							foreach (GameObject item2 in list)
							{
								try
								{
									NetworkObjectHelper.SafeDestroy(item2);
									num2++;
								}
								catch (Exception ex)
								{
									Debug.LogWarning((object)("FiresAdminPrefabs: fap_clean_stale_clutter — SafeDestroy threw on '" + ((item2 != null) ? ((Object)item2).name : null) + "': " + ex.Message));
								}
							}
							args.Context.AddString($"[FAP] fap_clean_stale_clutter APPLY: destroyed {num2} stale clutter ZDO(s) via SafeDestroy. World save will reflect the cleanup on next save tick (or zone unload). Re-run after a /save command to persist the cleanup.");
						}
					}
				};
				<>c.<>9__1_14 = val15;
				obj15 = (object)val15;
			}
			new ConsoleCommand("fap_clean_stale_clutter", "Walk ZNetScene.m_instances, find stale persistent ZDOs for clutter-effect prefabs (D_sfx_rock_destroy, instanced_small_rock1, etc.) saved into the world before the SpawnInstanced m_forceDisableInit fix landed, and destroy them via SafeDestroy. Usage: fap_clean_stale_clutter [apply]. Without 'apply', dry-run only.", (ConsoleEvent)obj15, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj16 = <>c.<>9__1_15;
			if (obj16 == null)
			{
				ConsoleEvent val16 = delegate(ConsoleEventArgs args)
				{
					//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
					if (args.Length < 2)
					{
						args.Context.AddString("[FAP] usage: fap_bake_diagnose <blueprintName> [nameFilter]");
					}
					else
					{
						ZNetScene instance = ZNetScene.instance;
						if ((Object)(object)instance == (Object)null)
						{
							args.Context.AddString("[FAP] fap_bake_diagnose: ZNetScene not ready — run after fully spawning in.");
						}
						else
						{
							string text = args[1];
							string nameFilter = ((args.Length >= 3) ? args[2] : null);
							VABlueprintLoader.Blueprint blueprint = VABlueprintLoader.LoadByName(text);
							if (blueprint == null)
							{
								args.Context.AddString("[FAP] fap_bake_diagnose: blueprint '" + text + "' not found in any probed blueprint folder.");
							}
							else
							{
								blueprint.Center(string.Empty);
								int lineCount = 0;
								Action<string> emit = delegate(string line)
								{
									args.Context.AddString(line);
									Debug.Log((object)("FiresAdminPrefabs: [bake_diagnose] " + line));
									lineCount++;
								};
								try
								{
									VAFapBlueprintMeshBaker.DiagnoseBlueprintBake(blueprint, instance, nameFilter, emit);
								}
								catch (Exception ex)
								{
									args.Context.AddString("[FAP] fap_bake_diagnose threw: " + ex.Message);
									Debug.LogWarning((object)string.Format("{0}: fap_bake_diagnose threw: {1}", "FiresAdminPrefabs", ex));
									return;
								}
								args.Context.AddString($"[FAP] fap_bake_diagnose: {lineCount} line(s) — full detail in LogOutput.log (search '[bake_diagnose]').");
							}
						}
					}
				};
				<>c.<>9__1_15 = val16;
				obj16 = (object)val16;
			}
			new ConsoleCommand("fap_bake_diagnose", "Diagnose why a blueprint's pieces do/don't land in the combined mesh, with no rebake. Usage: fap_bake_diagnose <blueprintName> [nameFilter]. Detail goes to LogOutput.log; a summary prints here.", (ConsoleEvent)obj16, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			object obj17 = <>c.<>9__1_16;
			if (obj17 == null)
			{
				ConsoleEvent val17 = delegate(ConsoleEventArgs args)
				{
					if ((Object)(object)VaPieces.Instance == (Object)null)
					{
						args.Context.AddString("[FAP] fap_bake_all: plugin instance not available — try again after the world is fully loaded.");
					}
					else
					{
						args.Context.AddString("[FAP] fap_bake_all: baking ALL blueprints + writing the diagnostic report. Watch the message hud; this can take many minutes. Report → config/FiresAdminPrefabs/Diagnostics/bake_all_report.txt");
						((MonoBehaviour)VaPieces.Instance).StartCoroutine(VAHammerRightClickPatches.BakeAllWithDiagnosticCoroutine());
					}
				};
				<>c.<>9__1_16 = val17;
				obj17 = (object)val17;
			}
			new ConsoleCommand("fap_bake_all", "Bake EVERY registered blueprint and write a full diagnostic report to config/FiresAdminPrefabs/Diagnostics/bake_all_report.txt (unresolved materials, zero-geometry pieces, churn-risk materials). Can take many minutes.", (ConsoleEvent)obj17, true, false, false, false, false, false, (ConsoleOptionsFetcher)null, false, false, false);
			LogRegisteredCommands();
		}

		private static void LogRegisteredCommands()
		{
			if (!(AccessTools.Field(typeof(Terminal), "commands")?.GetValue(null) is IDictionary dictionary))
			{
				Debug.Log((object)"FiresAdminPrefabs: VAFapConsoleCommands registered — the terminal's command table could not be read, so the list is unavailable.");
				return;
			}
			List<string> list = new List<string>();
			foreach (object key in dictionary.Keys)
			{
				string text = key as string;
				if (!string.IsNullOrEmpty(text) && text.StartsWith("fap_", StringComparison.Ordinal))
				{
					list.Add(text);
				}
			}
			list.Sort(StringComparer.Ordinal);
			Debug.Log((object)string.Format("{0}: VAFapConsoleCommands registered {1}: {2}.", "FiresAdminPrefabs", list.Count, string.Join(", ", list.ToArray())));
		}

		private static void ImportFromYoink(ConsoleEventArgs args, bool force)
		{
			string configPath = Paths.ConfigPath;
			string text = Path.Combine(configPath, "FiresSteamyDumps", "Yoink", "ScanMe", "Bundles");
			string text2 = Path.Combine(configPath, "FiresSteamyDumps", "Yoink", "ScanMe", "Manifests");
			string assetsDir = VAExternalBundleLoader.AssetsDir;
			string manifestsDir = VAExternalBundleLoader.ManifestsDir;
			args.Context.AddString("[FAP] fap_import_yoink: " + (force ? "FORCE OVERWRITE" : "skip-existing") + " mode");
			int num = 0;
			int num2 = 0;
			int num3 = 0;
			int num4 = 0;
			if (Directory.Exists(text))
			{
				try
				{
					Directory.CreateDirectory(assetsDir);
				}
				catch
				{
				}
				string[] files = Directory.GetFiles(text);
				foreach (string text3 in files)
				{
					string fileName = Path.GetFileName(text3);
					if (fileName.Equals("README.txt", StringComparison.OrdinalIgnoreCase))
					{
						continue;
					}
					string text4 = Path.Combine(assetsDir, fileName);
					if (File.Exists(text4) && !force)
					{
						num2++;
						continue;
					}
					try
					{
						File.Copy(text3, text4, overwrite: true);
						num++;
					}
					catch (Exception ex)
					{
						args.Context.AddString("  bundle '" + fileName + "': copy failed: " + ex.Message);
					}
				}
				args.Context.AddString($"[FAP] Bundles: copied {num}, skipped {num2} (already present).");
			}
			else
			{
				args.Context.AddString("[FAP] No bundles to import — source folder missing: " + text);
			}
			if (Directory.Exists(text2))
			{
				try
				{
					Directory.CreateDirectory(manifestsDir);
				}
				catch
				{
				}
				string[] files = Directory.GetFiles(text2, "*.manifest.json");
				foreach (string text5 in files)
				{
					string fileName2 = Path.GetFileName(text5);
					string text6 = Path.Combine(manifestsDir, fileName2);
					if (File.Exists(text6) && !force)
					{
						num4++;
						continue;
					}
					try
					{
						File.Copy(text5, text6, overwrite: true);
						num3++;
					}
					catch (Exception ex2)
					{
						args.Context.AddString("  manifest '" + fileName2 + "': copy failed: " + ex2.Message);
					}
				}
				args.Context.AddString($"[FAP] Manifests: copied {num3}, skipped {num4} (already present).");
			}
			else
			{
				args.Context.AddString("[FAP] No manifests to import — source folder missing: " + text2);
			}
			if (num > 0 || num3 > 0)
			{
				args.Context.AddString("[FAP] Import complete. Re-enter the world (or restart Valheim) to load the new bundles + manifests.");
			}
			else if (num2 > 0 || num4 > 0)
			{
				args.Context.AddString("[FAP] Nothing new to import. Use 'fap_import_yoink force' to overwrite existing destination files.");
			}
		}

		private static string DescribeFile(string label, string path)
		{
			if (!File.Exists(path))
			{
				return "  " + label + ": (none yet) " + path;
			}
			try
			{
				string text = File.ReadAllText(path);
				int num = 0;
				string[] array = text.Split(new char[1] { '\n' });
				for (int i = 0; i < array.Length; i++)
				{
					if (array[i].StartsWith("- ", StringComparison.Ordinal))
					{
						num++;
					}
				}
				return $"  {label}: {num} entries, {text.Length} chars  {path}";
			}
			catch (Exception ex)
			{
				return "  " + label + ": read error: " + ex.Message;
			}
		}

		private static void ReportOrphanGroups(ConsoleEventArgs args, List<VAFapOrphanZdoScan.OrphanGroup> groups)
		{
			//IL_0080: Unknown result type (might be due to invalid IL or missing references)
			//IL_0085: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dc: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e8: Unknown result type (might be due to invalid IL or missing references)
			int num = 0;
			int num2 = 0;
			foreach (VAFapOrphanZdoScan.OrphanGroup group in groups)
			{
				num += group.ZdoCount;
				if (group.IsNamed)
				{
					num2++;
				}
			}
			args.Context.AddString($"[FAP] {groups.Count} orphaned prefab(s), {num} ZDO(s):");
			foreach (VAFapOrphanZdoScan.OrphanGroup group2 in groups)
			{
				Vector3 samplePosition = group2.SamplePosition;
				args.Context.AddString("  " + (group2.IsNamed ? group2.PrefabName : "<unknown prefab>") + $"  hash={group2.PrefabHash}  {group2.ZdoCount} ZDO(s)" + $"  near ({samplePosition.x:F0}, {samplePosition.y:F0}, {samplePosition.z:F0})");
			}
			args.Context.AddString($"[FAP] 'fap_orphan_zdo purge' would delete the {num2} FAP prefab(s) named above. " + "Use 'purge <hash>' for one prefab, or 'purge all' for every orphan.");
		}

		private static List<int> SelectOrphanHashes(ConsoleEventArgs args, List<VAFapOrphanZdoScan.OrphanGroup> groups, string target)
		{
			List<int> list = new List<int>();
			if (!string.IsNullOrEmpty(target) && !string.Equals(target, "all", StringComparison.OrdinalIgnoreCase))
			{
				if (!int.TryParse(target, out var result))
				{
					args.Context.AddString("[FAP] '" + target + "' is not a prefab hash. Pass a hash from the report, or 'all'.");
					return null;
				}
				foreach (VAFapOrphanZdoScan.OrphanGroup group in groups)
				{
					if (group.PrefabHash == result)
					{
						list.Add(result);
					}
				}
				if (list.Count == 0)
				{
					args.Context.AddString($"[FAP] No orphaned ZDOs with hash {result}.");
				}
				if (list.Count != 0)
				{
					return list;
				}
				return null;
			}
			bool flag = string.Equals(target, "all", StringComparison.OrdinalIgnoreCase);
			foreach (VAFapOrphanZdoScan.OrphanGroup group2 in groups)
			{
				if (flag || group2.IsNamed)
				{
					list.Add(group2.PrefabHash);
				}
			}
			if (list.Count == 0)
			{
				args.Context.AddString("[FAP] Nothing purged: every orphan belongs to a prefab FAP cannot name. They are usually another mod's objects, so name a hash explicitly or use 'purge all'.");
				return null;
			}
			return list;
		}

		private static bool IsLocalAdminForWorldEdit()
		{
			try
			{
				return AdminSyncing.IsLocalAdmin();
			}
			catch
			{
				return false;
			}
		}
	}
}
namespace VerdantsAscent.Compat
{
	public static class VAWackyDatabaseCompat
	{
		[HarmonyPatch]
		private static class DataHelpers_FindPieceObjectName_Patch
		{
			private static bool Prepare()
			{
				return ResolveFindPieceMethod() != null;
			}

			private static MethodBase TargetMethod()
			{
				return ResolveFindPieceMethod();
			}

			private static void Postfix(string name, ref GameObject __result)
			{
				if (!Enabled)
				{
					return;
				}
				if (IsUnsafeCloneSource(name))
				{
					if ((Object)(object)__result != (Object)null)
					{
						Debug.LogWarning((object)("FiresAdminPrefabs: WackysDatabase asked to clone '" + name + "', which only exists while FAP holds its data. Refused — a clone of it would save an unresolvable ZDO."));
					}
					__result = null;
				}
				else if (!((Object)(object)__result != (Object)null))
				{
					GameObject val = VAVanillaPieceManager.RegisterSinglePieceOnDemand(name);
					if (!((Object)(object)val == (Object)null))
					{
						__result = val;
						Debug.Log((object)("FiresAdminPrefabs: WackysDatabase asked for '" + name + "' before the hammer scan reached it — registered it now so the clone resolves."));
					}
				}
			}
		}

		[HarmonyPatch(typeof(ZNetScene), "Awake")]
		private static class ZNetScene_Awake_WdbCompat_Patch
		{
			[HarmonyPriority(0)]
			private static void Postfix()
			{
				ResetCaches();
				PublishCloneBlacklist();
			}
		}

		private const string ApiTypeName = "API.WackyAPI, WackysDatabase";

		private const string DataHelpersTypeName = "wackydatabase.Datas.DataHelpers, WackysDatabase";

		private const string FindPieceMethodName = "FindPieceObjectName";

		private const string AddBlacklistCloneMethodName = "AddBlacklistClone";

		private const string GetClonedMapMethodName = "GetClonedMap";

		private const string BakeParentPrefabName = "VA_BakeParent";

		private static MethodInfo _addBlacklistClone;

		private static MethodInfo _getClonedMap;

		private static bool _apiResolved;

		private static bool _apiPresent;

		private static readonly Dictionary<string, string> _cloneSourceByName = new Dictionary<string, string>(StringComparer.Ordinal);

		private static readonly HashSet<string> _blacklistPublished = new HashSet<string>(StringComparer.Ordinal);

		public static bool IsInstalled => ResolveApi();

		private static bool Enabled
		{
			get
			{
				if (ConfigManager.Instance?.configWackyDatabaseCompat?.Value ?? true)
				{
					return ResolveApi();
				}
				return false;
			}
		}

		private static bool ResolveApi()
		{
			if (_apiResolved)
			{
				return _apiPresent;
			}
			_apiResolved = true;
			try
			{
				Type type = Type.GetType("API.WackyAPI, WackysDatabase");
				if (type == null)
				{
					return false;
				}
				_addBlacklistClone = type.GetMethod("AddBlacklistClone", BindingFlags.Static | BindingFlags.Public);
				_getClonedMap = type.GetMethod("GetClonedMap", BindingFlags.Static | BindingFlags.Public);
				_apiPresent = _addBlacklistClone != null || _getClonedMap != null;
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("FiresAdminPrefabs: WackysDatabase API lookup failed: " + ex.Message));
			}
			return _apiPresent;
		}

		public static bool IsUnsafeCloneSource(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return false;
			}
			if (prefabName.StartsWith("BP_", StringComparison.Ordinal))
			{
				return true;
			}
			if (prefabName.StartsWith("FAP_", StringComparison.Ordinal))
			{
				return true;
			}
			if (string.Equals(prefabName, "VA_BakeParent", StringComparison.Ordinal))
			{
				return true;
			}
			return VABakeBundle.TryGetCacheKeyForPrefab(prefabName) != null;
		}

		public static void PublishCloneBlacklist()
		{
			if (!Enabled || _addBlacklistClone == null)
			{
				return;
			}
			int num = 0;
			foreach (string item in CollectUnsafeCloneSources())
			{
				if (_blacklistPublished.Add(item))
				{
					try
					{
						_addBlacklistClone.Invoke(null, new object[1] { item });
						num++;
					}
					catch (Exception ex)
					{
						Debug.LogWarning((object)("FiresAdminPrefabs: WackysDatabase blacklist rejected '" + item + "': " + ex.Message));
					}
				}
			}
			if (num > 0)
			{
				Debug.Log((object)string.Format("{0}: WackysDatabase — refused {1} FAP prefab(s) as clone sources ({2} total).", "FiresAdminPrefabs", num, _blacklistPublished.Count));
			}
		}

		private static IEnumerable<string> CollectUnsafeCloneSources()
		{
			yield return "VA_BakeParent";
			foreach (string registeredBlueprintPrefabName in VABlueprintHammerIntegration.RegisteredBlueprintPrefabNames)
			{
				yield return registeredBlueprintPrefabName;
			}
			foreach (string registeredBundlePrefabName in VABakeBundle.RegisteredBundlePrefabNames)
			{
				yield return registeredBundlePrefabName;
			}
			ZNetScene instance = ZNetScene.instance;
			if (instance?.m_prefabs == null)
			{
				yield break;
			}
			foreach (GameObject prefab in instance.m_prefabs)
			{
				if (!((Object)(object)prefab == (Object)null) && ((Object)prefab).name.StartsWith("FAP_", StringComparison.Ordinal))
				{
					yield return ((Object)prefab).name;
				}
			}
		}

		public static GameObject ResolveCloneSource(GameObject pieceObject)
		{
			if ((Object)(object)pieceObject == (Object)null)
			{
				return pieceObject;
			}
			if (!Enabled || _getClonedMap == null)
			{
				return pieceObject;
			}
			string text = LookupCloneSourceName(((Object)pieceObject).name);
			if (string.IsNullOrEmpty(text))
			{
				return pieceObject;
			}
			ZNetScene instance = ZNetScene.instance;
			GameObject val = ((instance != null) ? instance.GetPrefab(text) : null);
			if (!((Object)(object)val == (Object)null))
			{
				return val;
			}
			return pieceObject;
		}

		private static string LookupCloneSourceName(string prefabName)
		{
			if (string.IsNullOrEmpty(prefabName))
			{
				return null;
			}
			if (_cloneSourceByName.TryGetValue(prefabName, out var value))
			{
				return value;
			}
			string text = string.Empty;
			try
			{
				text = (_getClonedMap.Invoke(null, new object[1] { prefabName }) as string) ?? string.Empty;
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("FiresAdminPrefabs: WackysDatabase clone lookup failed for '" + prefabName + "': " + ex.Message));
			}
			_cloneSourceByName[prefabName] = text;
			return text;
		}

		internal static void ResetCaches()
		{
			_cloneSourceByName.Clear();
			_blacklistPublished.Clear();
		}

		private static MethodBase ResolveFindPieceMethod()
		{
			try
			{
				return Type.GetType("wackydatabase.Datas.DataHelpers, WackysDatabase")?.GetMethod("FindPieceObjectName", BindingFlags.Static | BindingFlags.Public);
			}
			catch
			{
				return null;
			}
		}
	}
}
namespace VerdantsAscent.Sync
{
	public static class VAFapSyncManifest
	{
		[Serializable]
		public class ManifestEntry
		{
			public string Path;

			public long Size;

			public long MtimeTicks;

			public string Sha256;
		}

		[Serializable]
		public class Manifest
		{
			public int Version = 1;

			public List<ManifestEntry> Entries = new List<ManifestEntry>();
		}

		public static readonly string[] SyncedFolders = new string[4] { "Assets", "Manifests", "Shaders", "Textures" };

		private const int ManifestFormatVersion = 1;

		private const int HashReadBufferBytes = 1048576;

		private const string CacheFileName = ".sync_hashes.json";

		private static Dictionary<string, ManifestEntry> _cache;

		private static bool _cacheLoaded;

		private static bool _cacheDirty;

		private static readonly object _buildLock = new object();

		public static string RootDir => Path.Combine(Paths.ConfigPath, "FiresAdminPrefabs");

		public static string CacheFilePath => Path.Combine(RootDir, ".sync_hashes.json");

		public static IEnumerator BuildLocalManifestAsync(Action<Manifest, Exception> onComplete)
		{
			Manifest built = null;
			Exception failure = null;
			bool finished = false;
			if (!ThreadPool.QueueUserWorkItem(delegate
			{
				try
				{
					built = BuildLocalManifest();
				}
				catch (Exception ex2)
				{
					failure = ex2;
				}
				finally
				{
					Volatile.Write(ref finished, value: true);
				}
			}))
			{
				try
				{
					built = BuildLocalManifest();
				}
				catch (Exception ex)
				{
					failure = ex;
				}
				Volatile.Write(ref finished, value: true);
			}
			while (!Volatile.Read(in finished))
			{
				yield return null;
			}
			onComplete?.Invoke(built, failure);
		}

		public static Manifest BuildLocalManifest()
		{
			lock (_buildLock)
			{
				return BuildLocalManifestCore();
			}
		}

		private static Manifest BuildLocalManifestCore()
		{
			LoadCacheIfNeeded();
			Manifest manifest = new Manifest();
			string rootDir = RootDir;
			if (!Directory.Exists(rootDir))
			{
				VAFapThreadLog.Info("[FAP Sync] RootDir '" + rootDir + "' does not exist — manifest is empty.");
				return manifest;
			}
			int num = 0;
			int num2 = 0;
			string[] syncedFolders = SyncedFolders;
			foreach (string path in syncedFolders)
			{
				string path2 = Path.Combine(rootDir, path);
				if (!Directory.Exists(path2))
				{
					continue;
				}
				foreach (string item in Directory.EnumerateFiles(path2, "*", SearchOption.AllDirectories))
				{
					try
					{
						if (!IsReparsePoint(item))
						{
							FileInfo fileInfo = new FileInfo(item);
							long length = fileInfo.Length;
							long ticks = fileInfo.LastWriteTimeUtc.Ticks;
							string text = item.Substring(rootDir.Length + 1).Replace('\\', '/');
							if (TryGetCachedHash(text, length, ticks, out var sha))
							{
								num++;
							}
							else
							{
								sha = ComputeSha256(item);
								num2++;
								_cache[text] = new ManifestEntry
								{
									Path = text,
									Size = length,
									MtimeTicks = ticks,
									Sha256 = sha
								};
								_cacheDirty = true;
							}
							manifest.Entries.Add(new ManifestEntry
							{
								Path = text,
								Size = length,
								MtimeTicks = ticks,
								Sha256 = sha
							});
						}
					}
					catch (Exception ex)
					{
						VAFapThreadLog.Warning("[FAP Sync] Skipping '" + item + "' during manifest build: " + ex.Message);
					}
				}
			}
			int num3 = PruneCacheEntriesMissingFrom(manifest);
			if (_cacheDirty)
			{
				SaveCache();
			}
			VAFapThreadLog.Info($"[FAP Sync] BuildLocalManifest — {manifest.Entries.Count} file(s) " + $"({num} cached, {num2} re-hashed, {num3} stale entries pruned).");
			return manifest;
		}

		private static bool IsReparsePoint(string path)
		{
			return (File.GetAttributes(path) & FileAttributes.ReparsePoint) != 0;
		}

		private static bool TryGetCachedHash(string relPath, long size, long mtime, out string sha)
		{
			sha = null;
			if (!_cache.TryGetValue(relPath, out var value))
			{
				return false;
			}
			if (value.Size != size || value.MtimeTicks != mtime)
			{
				return false;
			}
			if (string.IsNullOrEmpty(value.Sha256))
			{
				return false;
			}
			sha = value.Sha256;
			return true;
		}

		private static int PruneCacheEntriesMissingFrom(Manifest manifest)
		{
			HashSet<string> hashSet = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
			foreach (ManifestEntry entry in manifest.Entries)
			{
				hashSet.Add(entry.Path);
			}
			List<string> list = new List<string>();
			foreach (string key in _cache.Keys)
			{
				if (!hashSet.Contains(key))
				{
					list.Add(key);
				}
			}
			foreach (string item in list)
			{
				_cache.Remove(item);
				_cacheDirty = true;
			}
			return list.Count;
		}

		public static List<ManifestEntry> Diff(Manifest client, Manifest server)
		{
			Dictionary<string, ManifestEntry> dictionary = new Dictionary<string, ManifestEntry>(StringComparer.OrdinalIgnoreCase);
			if (client?.Entries != null)
			{
				foreach (ManifestEntry entry in client.Entries)
				{
					dictionary[entry.Path] = entry;
				}
			}
			List<ManifestEntry> list = new List<ManifestEntry>();
			if (server?.Entries == null)
			{
				return list;
			}
			foreach (ManifestEntry entry2 in server.Entries)
			{
				if (entry2 != null && !string.IsNullOrEmpty(entry2.Path) && !string.IsNullOrEmpty(entry2.Sha256) && (!dictionary.TryGetValue(entry2.Path, out var value) || !string.Equals(value.Sha256, entry2.Sha256, StringComparison.OrdinalIgnoreCase)))
				{
					list.Add(entry2);
				}
			}
			return list;
		}

		public static string ComputeSha256(string absPath)
		{
			using FileStream inputStream = new FileStream(absPath, FileMode.Open, FileAccess.Read, FileShare.Read, 1048576, FileOptions.SequentialScan);
			using SHA256 sHA = SHA256.Create();
			byte[] array = sHA.ComputeHash(inputStream);
			StringBuilder stringBuilder = new StringBuilder(array.Length * 2);
			for (int i = 0; i < array.Length; i++)
			{
				stringBuilder.Append(array[i].ToString("x2"));
			}
			return stringBuilder.ToString();
		}

		public static byte[] Serialize(Manifest manifest)
		{
			string s = JsonConvert.SerializeObject((object)manifest);
			return Encoding.UTF8.GetBytes(s);
		}

		public static Manifest Deserialize(byte[] data)
		{
			if (data == null || data.Length == 0)
			{
				return new Manifest();
			}
			try
			{
				return JsonConvert.DeserializeObject<Manifest>(Encoding.UTF8.GetString(data)) ?? new Manifest();
			}
			catch (Exception ex)
			{
				VAFapThreadLog.Warning("[FAP Sync] Manifest.Deserialize failed: " + ex.Message);
				return new Manifest();
			}
		}

		private static void LoadCacheIfNeeded()
		{
			if (_cacheLoaded)
			{
				return;
			}
			_cacheLoaded = true;
			_cache = new Dictionary<string, ManifestEntry>(StringComparer.OrdinalIgnoreCase);
			string cacheFilePath = CacheFilePath;
			if (!File.Exists(cacheFilePath))
			{
				return;
			}
			try
			{
				Manifest manifest = JsonConvert.DeserializeObject<Manifest>(File.ReadAllText(cacheFilePath));
				if (manifest?.Entries != null)
				{
					foreach (ManifestEntry entry in manifest.Entries)
					{
						if (entry != null && !string.IsNullOrEmpty(entry.Path))
						{
							_cache[entry.Path] = entry;
						}
					}
				}
				VAFapThreadLog.Info($"[FAP Sync] Loaded {_cache.Count} cached hash(es) from {cacheFilePath}.");
			}
			catch (Exception ex)
			{
				VAFapThreadLog.Warning("[FAP Sync] Cache load failed (" + cacheFilePath + "): " + ex.Message + " — starting with empty cache.");
				_cache.Clear();
			}
		}

		private static void SaveCache()
		{
			try
			{
				if (!Directory.Exists(RootDir))
				{
					Directory.CreateDirectory(RootDir);
				}
				Manifest manifest = new Manifest();
				foreach (KeyValuePair<string, ManifestEntry> item in _cache)
				{
					manifest.Entries.Add(item.Value);
				}
				string cacheFilePath = CacheFilePath;
				string text = cacheFilePath + ".tmp";
				File.WriteAllText(text, JsonConvert.SerializeObject((object)manifest, (Formatting)0));
				if (File.Exists(cacheFilePath))
				{
					File.Delete(cacheFilePath);
				}
				File.Move(text, cacheFilePath);
				_cacheDirty = false;
			}
			catch (Exception ex)
			{
				VAFapThreadLog.Warning("[FAP Sync] Cache save failed: " + ex.Message);
			}
		}

		public static void InvalidateCache()
		{
			_cache = null;
			_cacheLoaded = false;
			_cacheDirty = false;
			try
			{
				if (File.Exists(CacheFilePath))
				{
					File.Delete(CacheFilePath);
				}
			}
			catch (Exception ex)
			{
				VAFapThreadLog.Warning("[FAP Sync] InvalidateCache delete failed: " + ex.Message);
			}
		}
	}
	public static class VAFapSyncChunker
	{
		private const byte FLAG_COMPRESSED = 1;

		private const byte FLAG_FRAGMENTED = 2;

		public const int ChunkSize = 250000;

		private const int CompressionThreshold = 10000;

		private const int FileReadBufferBytes = 1048576;

		public const int StreamHighWaterBytes = 131072;

		public const float StreamStallTimeoutSec = 30f;

		private const long ReassemblyTtlTicks = 600000000L;

		private static readonly Dictionary<string, SortedDictionary<int, byte[]>> _reassemblyCache = new Dictionary<string, SortedDictionary<int, byte[]>>();

		private static readonly List<KeyValuePair<long, string>> _cacheExpirations = new List<KeyValuePair<long, string>>();

		private static long _packageCounter = 0L;

		public static IEnumerator SendToPeer(ZNetPeer peer, string rpcName, byte[] payload, bool compress)
		{
			if (peer == null || payload == null || !peer.IsReady())
			{
				yield break;
			}
			byte[] data = payload;
			bool didCompress = false;
			if (compress && data.Length > 10000)
			{
				try
				{
					data = Deflate(data);
					didCompress = true;
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[FAP Sync] Compress failed for " + rpcName + ": " + ex.Message + " — sending uncompressed."));
					didCompress = false;
					data = payload;
				}
			}
			if (data.Length > 250000)
			{
				int count = (data.Length + 250000 - 1) / 250000;
				long packageId = ++_packageCounter;
				for (int i = 0; i < count; i++)
				{
					foreach (bool item in WaitForQueue(peer))
					{
						yield return item;
					}
					if (peer.m_socket.IsConnected())
					{
						ZPackage val = new ZPackage();
						byte b = 2;
						if (didCompress)
						{
							b |= 1;
						}
						val.Write(b);
						val.Write(packageId);
						val.Write(i);
						val.Write(count);
						int num = 250000 * i;
						int num2 = Math.Min(250000, data.Length - num);
						byte[] array = new byte[num2];
						Buffer.BlockCopy(data, num, array, 0, num2);
						val.Write(array);
						InvokeRpc(peer, rpcName, val);
						if (i < count - 1)
						{
							yield return true;
						}
						continue;
					}
					break;
				}
				yield break;
			}
			foreach (bool item2 in WaitForQueue(peer))
			{
				yield return item2;
			}
			ZPackage val2 = new ZPackage();
			byte b2 = 0;
			if (didCompress)
			{
				b2 |= 1;
			}
			val2.Write(b2);
			val2.Write(data);
			InvokeRpc(peer, rpcName, val2);
		}

		private static byte[] Deflate(byte[] data)
		{
			using MemoryStream memoryStream = new MemoryStream();
			using (DeflateStream deflateStream = new DeflateStream(memoryStream, CompressionLevel.Optimal))
			{
				deflateStream.Write(data, 0, data.Length);
			}
			return memoryStream.ToArray();
		}

		private static byte[] Inflate(byte[] data)
		{
			using MemoryStream stream = new MemoryStream(data);
			using MemoryStream memoryStream = new MemoryStream();
			using DeflateStream deflateStream = new DeflateStream(stream, CompressionMode.Decompress);
			deflateStream.CopyTo(memoryStream);
			return memoryStream.ToArray();
		}

		public static IEnumerator StreamFileToPeer(ZNetPeer peer, string relPath, string sha256, string absPath, string rpcBegin = null, string rpcChunk = null, string rpcEnd = null)
		{
			rpcBegin = rpcBegin ?? "FiresAdminPrefabs AssetSync_FileBegin";
			rpcChunk = rpcChunk ?? "FiresAdminPrefabs AssetSync_FileChunk";
			rpcEnd = rpcEnd ?? "FiresAdminPrefabs AssetSync_FileEnd";
			if (peer == null || peer.m_socket == null || !peer.m_socket.IsConnected())
			{
				yield break;
			}
			FileStream fileStream = null;
			long length;
			int num;
			try
			{
				fileStream = new FileStream(absPath, FileMode.Open, FileAccess.Read, FileShare.Read, 1048576, FileOptions.SequentialScan);
				length = fileStream.Length;
				num = (int)((length != 0L) ? ((length + 250000 - 1) / 250000) : 0);
			}
			catch (Exception ex)
			{
				if (fileStream != null)
				{
					try
					{
						fileStream.Dispose();
					}
					catch
					{
					}
				}
				Debug.LogWarning((object)("[FAP Sync] Stream open failed '" + absPath + "': " + ex.Message));
				SendFileEnd(peer, rpcEnd, relPath, ok: false);
				yield break;
			}
			bool aborted = false;
			using (fileStream)
			{
				ZPackage val = new ZPackage();
				val.Write(relPath ?? string.Empty);
				val.Write(sha256 ?? string.Empty);
				val.Write(length);
				val.Write(num);
				InvokeRpc(peer, rpcBegin, val);
				byte[] buffer = new byte[250000];
				bool[] timedOut = new bool[1];
				int index = 0;
				while (true)
				{
					int read;
					try
					{
						read = fileStream.Read(buffer, 0, 250000);
					}
					catch (Exception ex2)
					{
						Debug.LogWarning((object)("[FAP Sync] Stream read failed '" + relPath + "': " + ex2.Message));
						aborted = true;
						break;
					}
					if (read <= 0)
					{
						break;
					}
					timedOut[0] = false;
					IEnumerator wait = AwaitDrain(peer, timedOut);
					while (wait.MoveNext())
					{
						yield return wait.Current;
					}
					if (timedOut[0] || peer.m_socket == null || !peer.m_socket.IsConnected())
					{
						aborted = true;
						break;
					}
					ZPackage val2 = new ZPackage();
					val2.Write(relPath ?? string.Empty);
					val2.Write(index);
					if (read == 250000)
					{
						val2.Write(buffer);
					}
					else
					{
						byte[] array = new byte[read];
						Buffer.BlockCopy(buffer, 0, array, 0, read);
						val2.Write(array);
					}
					InvokeRpc(peer, rpcChunk, val2);
					index++;
					yield return null;
				}
			}
			if (peer.m_socket != null && peer.m_socket.IsConnected())
			{
				SendFileEnd(peer, rpcEnd, relPath, !aborted);
			}
		}

		private static IEnumerator AwaitDrain(ZNetPeer peer, bool[] timedOut)
		{
			float deadline = Time.time + 30f;
			while (peer != null && peer.m_socket != null && peer.m_socket.IsConnected() && peer.m_socket.GetSendQueueSize() > 131072)
			{
				if (Time.time > deadline)
				{
					Debug.LogWarning((object)($"[FAP Sync] Peer {peer.m_uid} send queue wedged >{30f:F0}s; " + "aborting this file (player stays connected)."));
					timedOut[0] = true;
					break;
				}
				yield return null;
			}
		}

		private static void SendFileEnd(ZNetPeer peer, string rpcEnd, string relPath, bool ok)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			try
			{
				ZPackage val = new ZPackage();
				val.Write(relPath ?? string.Empty);
				val.Write(ok);
				InvokeRpc(peer, rpcEnd, val);
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[FAP Sync] SendFileEnd failed '" + relPath + "': " + ex.Message));
			}
		}

		private static IEnumerable<bool> WaitForQueue(ZNetPeer peer)
		{
			float timeout = Time.time + 30f;
			while (peer.m_socket != null && peer.m_socket.GetSendQueueSize() > 131072)
			{
				if (Time.time > timeout)
				{
					Debug.LogWarning((object)($"[FAP Sync] Peer {peer.m_uid} send queue slow to drain " + $"(>{30f:F0}s); proceeding without disconnect."));
					break;
				}
				yield return false;
			}
		}

		private static void InvokeRpc(ZNetPeer peer, string rpcName, ZPackage pkg)
		{
			try
			{
				if (peer != null && ZRoutedRpc.instance != null)
				{
					long num = (peer.m_server ? 0 : peer.m_uid);
					ZRoutedRpc.instance.InvokeRoutedRPC(num, rpcName, new object[1] { pkg });
				}
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)$"[FAP Sync] InvokeRpc failed ({rpcName} → {peer?.m_uid}): {ex.Message}");
			}
		}

		public static byte[] ReceiveFragment(long sender, ZPackage package)
		{
			if (package == null)
			{
				return null;
			}
			ExpireStaleReassemblies();
			byte b = package.ReadByte();
			byte[] array;
			if ((b & 2) != 0)
			{
				long num = package.ReadLong();
				string text = sender + ":" + num;
				if (!_reassemblyCache.TryGetValue(text, out var value))
				{
					value = new SortedDictionary<int, byte[]>();
					_reassemblyCache[text] = value;
					_cacheExpirations.Add(new KeyValuePair<long, string>(DateTimeOffset.Now.Ticks + 600000000, text));
				}
				int key = package.ReadInt();
				int num2 = package.ReadInt();
				value[key] = package.ReadByteArray();
				if (value.Count < num2)
				{
					return null;
				}
				_reassemblyCache.Remove(text);
				array = Concatenate(value.Values);
			}
			else
			{
				array = package.ReadByteArray();
			}
			if ((b & 1) != 0)
			{
				try
				{
					array = Inflate(array);
				}
				catch (Exception ex)
				{
					Debug.LogWarning((object)("[FAP Sync] Decompress failed: " + ex.Message + " — discarding payload."));
					return null;
				}
			}
			return array;
		}

		private static void ExpireStaleReassemblies()
		{
			long now = DateTimeOffset.Now.Ticks;
			_cacheExpirations.RemoveAll(delegate(KeyValuePair<long, string> expiration)
			{
				if (expiration.Key >= now)
				{
					return false;
				}
				_reassemblyCache.Remove(expiration.Value);
				return true;
			});
		}

		private static byte[] Concatenate(IEnumerable<byte[]> fragments)
		{
			int num = 0;
			foreach (byte[] fragment in fragments)
			{
				num += fragment.Length;
			}
			byte[] array = new byte[num];
			int num2 = 0;
			foreach (byte[] fragment2 in fragments)
			{
				Buffer.BlockCopy(fragment2, 0, array, num2, fragment2.Length);
				num2 += fragment2.Length;
			}
			return array;
		}

		public static List<ZNetPeer> GetRoutedPeers()
		{
			if (ZRoutedRpc.instance == null)
			{
				return null;
			}
			try
			{
				FieldInfo fieldInfo = AccessTools.Field(typeof(ZRoutedRpc), "m_peers");
				if (fieldInfo == null)
				{
					return null;
				}
				return (List<ZNetPeer>)fieldInfo.GetValue(ZRoutedRpc.instance);
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[FAP Sync] GetRoutedPeers reflection failed: " + ex.Message));
				return null;
			}
		}
	}
	public static class VAFapSyncRpcs
	{
		public const string RPC_CLIENT_MANIFEST = "FiresAdminPrefabs AssetSync_ClientManifest";

		public const string RPC_DELTA_LIST = "FiresAdminPrefabs AssetSync_DeltaList";

		public const string RPC_FILE_REQUEST = "FiresAdminPrefabs AssetSync_FileRequest";

		public const string RPC_FILE_BEGIN = "FiresAdminPrefabs AssetSync_FileBegin";

		public const string RPC_FILE_CHUNK = "FiresAdminPrefabs AssetSync_FileChunk";

		public const string RPC_FILE_END = "FiresAdminPrefabs AssetSync_FileEnd";

		public const string RPC_TRANSFER_DONE = "FiresAdminPrefabs AssetSync_TransferDone";

		public const string RPC_ADMIN_PUSH_BEGIN = "FiresAdminPrefabs AssetSync_AdminPushBegin";

		public const string RPC_ADMIN_PUSH_CHUNK = "FiresAdminPrefabs AssetSync_AdminPushChunk";

		public const string RPC_ADMIN_PUSH_END = "FiresAdminPrefabs AssetSync_AdminPushEnd";

		public const string RPC_ADMIN_PUSH_DONE = "FiresAdminPrefabs AssetSync_AdminPushDone";

		public const string RPC_ADMIN_PUSH_ACK = "FiresAdminPrefabs AssetSync_AdminPushAck";

		private const byte TransferDoneSentinel = 0;

		private static ZRoutedRpc _registeredOn;

		public static void RegisterAll()
		{
			ZRoutedRpc instance = ZRoutedRpc.instance;
			if (instance == null || _registeredOn == instance)
			{
				return;
			}
			try
			{
				instance.Register<ZPackage>("FiresAdminPrefabs AssetSync_ClientManifest", (Action<long, ZPackage>)OnClientManifest);
				instance.Register<ZPackage>("FiresAdminPrefabs AssetSync_DeltaList", (Action<long, ZPackage>)OnDeltaList);
				instance.Register<ZPackage>("FiresAdminPrefabs AssetSync_FileRequest", (Action<long, ZPackage>)OnFileRequest);
				instance.Register<ZPackage>("FiresAdminPrefabs AssetSync_FileBegin", (Action<long, ZPackage>)OnFileBegin);
				instance.Register<ZPackage>("FiresAdminPrefabs AssetSync_FileChunk", (Action<long, ZPackage>)OnFileChunk);
				instance.Register<ZPackage>("FiresAdminPrefabs AssetSync_FileEnd", (Action<long, ZPackage>)OnFileEnd);
				instance.Register<ZPackage>("FiresAdminPrefabs AssetSync_TransferDone", (Action<long, ZPackage>)OnTransferDone);
				instance.Register<ZPackage>("FiresAdminPrefabs AssetSync_AdminPushBegin", (Action<long, ZPackage>)OnAdminPushBegin);
				instance.Register<ZPackage>("FiresAdminPrefabs AssetSync_AdminPushChunk", (Action<long, ZPackage>)OnAdminPushChunk);
				instance.Register<ZPackage>("FiresAdminPrefabs AssetSync_AdminPushEnd", (Action<long, ZPackage>)OnAdminPushEnd);
				instance.Register<ZPackage>("FiresAdminPrefabs AssetSync_AdminPushDone", (Action<long, ZPackage>)OnAdminPushDone);
				instance.Register<ZPackage>("FiresAdminPrefabs AssetSync_AdminPushAck", (Action<long, ZPackage>)OnAdminPushAck);
				_registeredOn = instance;
				Debug.Log((object)$"[FAP Sync] Registered asset-sync RPCs (ZRoutedRpc #{((object)instance).GetHashCode()}).");
			}
			catch (Exception ex)
			{
				Debug.LogWarning((object)("[FAP Sync] RPC register failed: " + ex.Message));
			}
		}

		private static void OnClientManifest(long sender, ZPackage pkg)
		{
			if (IsServer())
			{
				ForwardReassembled("OnClientManifest", sender, pkg, delegate(byte[] payload)
				{
					VAFapAssetSync.Server_HandleClientManifest(sender, VAFapSyncManifest.Deserialize(payload));
				});
			}
		}

		private static void OnFileRequest(long sender, ZPackage pkg)
		{
			if (IsServer())
			{
				ForwardReassembled("OnFileRequest", sender, pkg, delegate(byte[] payload)
				{
					//IL_0007: Unknown result type (might be due to invalid IL or missing references)
					VAFapAssetSync.Server_HandleFileRequest(sender, new ZPackage(payload).ReadString());
				});
			}
		}

		private static void OnAdminPushBegin(long sender, ZPackage pkg)
		{
			if (IsServer())
			{
				Forward("OnAdminPushBegin", sender, delegate
				{
					string relPath = pkg.ReadString();
					string sha = pkg.ReadString();
					long size = pkg.ReadLong();
					int chunkCount = pkg.ReadInt();
					VAFapAssetSync.Server_HandleAdminPushBegin(sender, relPath, sha, size, chunkCount);
				});
			}
		}

		private static void OnAdminPushChunk(long sender, ZPackage pkg)
		{
			if (IsServer())
			{
				Forward("OnAdminPushChunk", sender, delegate
				{
					string relPath = pkg.ReadString();
					int index = pkg.ReadInt();
					byte[] bytes = pkg.ReadByteArray();
					VAFapAssetSync.Server_HandleAdminPushChunk(sender, relPath, index, bytes);
				});
			}
		}

		private static void OnAdminPushEnd(long sender, ZPackage pkg)
		{
			if (IsServer())
			{
				Forward("OnAdminPushEnd", sender, delegate
				{
					string relPath = pkg.ReadString();
					bool ok = pkg.ReadBool();
					VAFapAssetSync.Server_HandleAdminPushEnd(sender, relPath, ok);
				});
			}
		}

		private static void OnAdminPushDone(long sender, ZPackage pkg)
		{
			if (IsServer())
			{
				ForwardReassembled("OnAdminPushDone", sender, pkg, delegate(byte[] payload)
				{
					//IL_0007: Unknown result type (might be due to invalid IL or missing references)
					VAFapAssetSync.Server_HandleAdminPushDone(sender, new ZPackage(payload).ReadInt());
				});
			}
		}

		private static void OnDeltaList(long sender, ZPackage pkg)
		{
			if (!IsServer())
			{
				ForwardReassembled("OnDeltaList", sender, pkg, delegate(byte[] payload)
				{
					VAFapAssetSync.Client_HandleDeltaList(VAFapSyncManifest.Deserialize(payload).Entries);
				});
			}
		}

		private static void OnFileBegin(long sender, ZPackage pkg)
		{
			if (!IsServer())
			{
				Forward("OnFileBegin", sender, delegate
				{
					string relPath = pkg.ReadString();
					string expectedSha = pkg.ReadString();
					long size = pkg.ReadLong();
					int chunkCount = pkg.ReadInt();
					VAFapAssetSync.Client_HandleFileBegin(relPath, expectedSha, size, chunkCount);
				});
			}
		}

		private static void OnFileChunk(long sender, ZPackage pkg)
		{
			if (!IsServer())
			{
				Forward("OnFileChunk", sender, delegate
				{
					string relPath = pkg.ReadString();
					int index = pkg.ReadInt();
					byte[] bytes = pkg.ReadByteArray();
					VAFapAssetSync.Client_HandleFileChunk(relPath, index, bytes);
				});
			}
		}

		private static void OnFileEnd(long sender, ZPackage pkg)
		{
			if (!IsServer())
			{
				Forward("OnFileEnd", sender, delegate
				{
					string relPath = pkg.ReadString();
					bool ok = pkg.ReadBool();
					VAFapAssetSync.Client_HandleFileEnd(relPath, ok);
				});
			}
		}

		private static void OnTransferDone(long sender, ZPackage pkg)
		{
			if (!IsServer())
			{
				Forward("OnTransferDone", sender, VAFapAssetSync.Client_HandleTransferDone);
			}
		}

		private static void OnAdminPushAck(long sender, ZPackage pkg)
		{
			if (!IsServer())
			{
				ForwardReassembled("OnAdminPushAck", sender, pkg, delegate(byte[] payload)
				{
					//IL_0001: Unknown result type (might be due to invalid IL or missing references)
					//IL_0006: Unknown result type (might be due to invalid IL or missing references)
					ZPackage val = new ZPackage(payload);
					bool ok = val.ReadBool();
					string message = val.ReadString();
					VAFapAssetSync.Client_HandleAdminPushAck(ok, message);
				});
			}
		}

		private static void ForwardReas