Decompiled source of ItemVariantSync v1.0.1

BepInEx/plugins/ItemVariantSync/ItemVariantSync.dll

Decompiled 11 hours ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Logging;
using HarmonyLib;
using ItemVariantSync.Patches;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json.Linq;
using Unity.Collections;
using Unity.Netcode;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("ItemVariantSync")]
[assembly: AssemblyConfiguration("Debug")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+bdd05f79510b75023348ca1bd704b02628a3db46")]
[assembly: AssemblyProduct("ItemVariantSync")]
[assembly: AssemblyTitle("ItemVariantSync")]
[assembly: AssemblyVersion("1.0.0.0")]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace ItemVariantSync
{
	internal static class DawnLibBridge
	{
		private const string EventsTypeName = "Dawn.Internal.DawnItemSaveEvents";

		private const string ExtraDataKey = "LVS_variant";

		private static bool _initTried;

		internal static bool Active { get; private set; }

		internal static void TryInit()
		{
			if (_initTried)
			{
				return;
			}
			_initTried = true;
			try
			{
				Type type = AccessTools.TypeByName("Dawn.Internal.DawnItemSaveEvents");
				if (type == null)
				{
					Plugin.Log.LogInfo((object)"DawnLib not installed - variant persistence disabled (sync and icons unaffected).");
					return;
				}
				EventInfo eventInfo = type.GetEvent("OnCollectExtraSaveData", BindingFlags.Static | BindingFlags.Public);
				EventInfo eventInfo2 = type.GetEvent("OnLoadExtraSaveData", BindingFlags.Static | BindingFlags.Public);
				if (eventInfo == null || eventInfo2 == null)
				{
					Plugin.Log.LogWarning((object)"DawnLib found but its item-save events are missing (version change?) - variant persistence disabled.");
					return;
				}
				eventInfo.AddEventHandler(null, Delegate.CreateDelegate(eventInfo.EventHandlerType, typeof(DawnLibBridge).GetMethod("OnCollect", BindingFlags.Static | BindingFlags.NonPublic)));
				eventInfo2.AddEventHandler(null, Delegate.CreateDelegate(eventInfo2.EventHandlerType, typeof(DawnLibBridge).GetMethod("OnLoad", BindingFlags.Static | BindingFlags.NonPublic)));
				Active = true;
				Plugin.Log.LogInfo((object)"DawnLib item pipeline detected - variant persistence rides DawnLib's per-item save data.");
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"DawnLib integration failed to initialize - variant persistence disabled: {arg}");
			}
		}

		private static void OnCollect(GrabbableObject item, JObject extraData)
		{
			//IL_005f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0065: Expected O, but got Unknown
			try
			{
				NetworkObject val = default(NetworkObject);
				if (!((Object)(object)item == (Object)null) && extraData != null && ((Component)item).TryGetComponent<NetworkObject>(ref val) && val.IsSpawned && VariantRegistry.TryGet(val.NetworkObjectId, out var data) && !data.IsEmpty)
				{
					JObject val2 = new JObject();
					if (data.MeshIndex >= 0)
					{
						val2["m"] = JToken.op_Implicit(data.MeshIndex);
					}
					if (data.MaterialIndex >= 0)
					{
						val2["t"] = JToken.op_Implicit(data.MaterialIndex);
					}
					extraData["LVS_variant"] = (JToken)(object)val2;
				}
			}
			catch (Exception arg)
			{
				ManualLogSource log = Plugin.Log;
				object arg2;
				if (item == null)
				{
					arg2 = null;
				}
				else
				{
					GameObject gameObject = ((Component)item).gameObject;
					arg2 = ((gameObject != null) ? ((Object)gameObject).name : null);
				}
				log.LogWarning((object)$"Failed embedding variant data for '{arg2}': {arg}");
			}
		}

		private static void OnLoad(GrabbableObject item, JObject extraData)
		{
			try
			{
				if ((Object)(object)item == (Object)null || extraData == null)
				{
					return;
				}
				JToken obj = extraData["LVS_variant"];
				JObject val = (JObject)(object)((obj is JObject) ? obj : null);
				if (val == null)
				{
					return;
				}
				VariantData variantData = new VariantData();
				JToken obj2 = val["m"];
				variantData.MeshIndex = (short)((obj2 != null) ? obj2.ToObject<short>() : (-1));
				JToken obj3 = val["t"];
				variantData.MaterialIndex = (short)((obj3 != null) ? obj3.ToObject<short>() : (-1));
				VariantData variantData2 = variantData;
				if (variantData2.IsEmpty)
				{
					return;
				}
				NetworkObject val2 = default(NetworkObject);
				if (!((Component)item).TryGetComponent<NetworkObject>(ref val2) || !val2.IsSpawned)
				{
					Plugin.Log.LogWarning((object)("DawnLib restore fired for '" + ((Object)((Component)item).gameObject).name + "' before spawn - skipping (unexpected ordering)."));
					return;
				}
				VariantRegistry.Set(val2.NetworkObjectId, variantData2);
				VariantManager.Apply(item, variantData2);
				if (VariantManager.IsServer)
				{
					VariantManager.QueueBroadcast(val2.NetworkObjectId);
				}
			}
			catch (Exception arg)
			{
				ManualLogSource log = Plugin.Log;
				object arg2;
				if (item == null)
				{
					arg2 = null;
				}
				else
				{
					GameObject gameObject = ((Component)item).gameObject;
					arg2 = ((gameObject != null) ? ((Object)gameObject).name : null);
				}
				log.LogWarning((object)$"Failed restoring variant data for '{arg2}': {arg}");
			}
		}
	}
	internal static class RuntimeIconsBridge
	{
		private enum State
		{
			Unchecked,
			Ready,
			Unavailable
		}

		private struct PendingIcon
		{
			public Item Clone;

			public Sprite Fallback;

			public float Deadline;
		}

		private const string RuntimeIconsGuid = "com.github.lethalcompanymodding.runtimeicons";

		private const float RestoreTimeout = 10f;

		private static State _state;

		private static MethodInfo _enqueueMethod;

		private static Func<object> _resolveQueue;

		private static Func<object, object> _queueGetter;

		private static readonly List<PendingIcon> Pending = new List<PendingIcon>(8);

		internal static void OnCloneAssigned(GrabbableObject item, Item clone, Item baseItem, bool newClone)
		{
			try
			{
				if (!newClone || (Object)(object)item == (Object)null || (Object)(object)clone == (Object)null)
				{
					return;
				}
				EnsureInitialized();
				if (_state != State.Ready)
				{
					return;
				}
				object obj = _resolveQueue();
				if (obj != null && EnsureEnqueueResolved(obj))
				{
					Sprite val = (((Object)(object)clone.itemIcon != (Object)null) ? clone.itemIcon : baseItem?.itemIcon);
					clone.itemIcon = null;
					try
					{
						_enqueueMethod.Invoke(obj, new object[3] { item, val, 2L });
					}
					catch
					{
						clone.itemIcon = val;
						throw;
					}
					Pending.Add(new PendingIcon
					{
						Clone = clone,
						Fallback = val,
						Deadline = Time.unscaledTime + 10f
					});
				}
			}
			catch (Exception arg)
			{
				_state = State.Unavailable;
				Plugin.Log.LogWarning((object)$"RuntimeIcons integration disabled after error: {arg}");
			}
		}

		internal static void Tick()
		{
			if (Pending.Count == 0)
			{
				return;
			}
			float unscaledTime = Time.unscaledTime;
			for (int num = Pending.Count - 1; num >= 0; num--)
			{
				PendingIcon pendingIcon = Pending[num];
				bool flag;
				if ((Object)(object)pendingIcon.Clone == (Object)null || (Object)(object)pendingIcon.Clone.itemIcon != (Object)null)
				{
					flag = true;
				}
				else if (unscaledTime > pendingIcon.Deadline)
				{
					pendingIcon.Clone.itemIcon = pendingIcon.Fallback;
					flag = true;
				}
				else
				{
					flag = false;
				}
				if (flag)
				{
					int index = Pending.Count - 1;
					Pending[num] = Pending[index];
					Pending.RemoveAt(index);
				}
			}
		}

		private static void EnsureInitialized()
		{
			if (_state != State.Unchecked)
			{
				return;
			}
			_state = State.Unavailable;
			try
			{
				Type type = AccessTools.TypeByName("RuntimeIcons.RuntimeIcons");
				if (type == null)
				{
					Plugin.Log.LogInfo((object)"RuntimeIcons not loaded (type RuntimeIcons.RuntimeIcons not found) - variant icon rendering off; variant identities keep base icons.");
					return;
				}
				string text = type.Assembly.GetName().Version?.ToString() ?? "?";
				if (!Chainloader.PluginInfos.ContainsKey("com.github.lethalcompanymodding.runtimeicons"))
				{
					List<string> list = new List<string>();
					foreach (string key in Chainloader.PluginInfos.Keys)
					{
						if (key.IndexOf("runtime", StringComparison.OrdinalIgnoreCase) >= 0)
						{
							list.Add(key);
						}
					}
					Plugin.Log.LogInfo((object)("RuntimeIcons assembly " + text + " is loaded but GUID 'com.github.lethalcompanymodding.runtimeicons' is not in the plugin registry (similar keys: " + ((list.Count > 0) ? string.Join(", ", list) : "none") + "). Proceeding via type binding."));
				}
				Func<object> stageGetter = GetStaticGetter(type, "RenderingStage");
				if (stageGetter == null)
				{
					Plugin.Log.LogWarning((object)"RuntimeIcons binding failed at step RenderingStage (member missing - version drift). Icon rendering off.");
					return;
				}
				object obj = stageGetter();
				_resolveQueue = delegate
				{
					object obj3 = stageGetter();
					if (obj3 == null)
					{
						return (object)null;
					}
					EnsureQueueAccessors(obj3);
					return _queueGetter?.Invoke(obj3);
				};
				if (obj != null)
				{
					EnsureQueueAccessors(obj);
					if (_queueGetter == null)
					{
						Plugin.Log.LogWarning((object)("RuntimeIcons binding failed at step CameraQueue (member missing on " + obj.GetType().Name + " - version drift). Icon rendering off."));
						return;
					}
					object obj2 = _queueGetter(obj);
					if (obj2 != null && !EnsureEnqueueResolved(obj2))
					{
						Plugin.Log.LogWarning((object)"RuntimeIcons binding failed at step EnqueueObject (signature changed - version drift). Icon rendering off.");
						return;
					}
				}
				_state = State.Ready;
				Plugin.Log.LogInfo((object)("RuntimeIcons " + text + " bound - per-variant identities get their own rendered icons."));
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"RuntimeIcons integration unavailable (exception during binding): {arg}");
			}
		}

		private static void EnsureQueueAccessors(object stage)
		{
			if (_queueGetter != null || stage == null)
			{
				return;
			}
			Type type = stage.GetType();
			PropertyInfo prop = type.GetProperty("CameraQueue", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (prop != null && prop.CanRead)
			{
				_queueGetter = (object o) => prop.GetValue(o);
				return;
			}
			FieldInfo field = type.GetField("CameraQueue", BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				_queueGetter = (object o) => field.GetValue(o);
			}
		}

		private static bool EnsureEnqueueResolved(object queue)
		{
			if (_enqueueMethod != null)
			{
				return true;
			}
			_enqueueMethod = queue.GetType().GetMethod("EnqueueObject", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[3]
			{
				typeof(GrabbableObject),
				typeof(Sprite),
				typeof(long)
			}, null);
			return _enqueueMethod != null;
		}

		private static Func<object> GetStaticGetter(Type type, string memberName)
		{
			PropertyInfo prop = type.GetProperty(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (prop != null && prop.CanRead)
			{
				return () => prop.GetValue(null);
			}
			FieldInfo field = type.GetField(memberName, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
			if (field != null)
			{
				return () => field.GetValue(null);
			}
			return null;
		}
	}
	internal static class Messaging
	{
		[CompilerGenerated]
		private static class <>O
		{
			public static HandleNamedMessageDelegate <0>__OnAssignReceived;

			public static HandleNamedMessageDelegate <1>__OnRequestReceived;
		}

		private const string AssignMsg = "LVS_Assign";

		private const string RequestMsg = "LVS_Request";

		private const byte ProtocolVersion = 2;

		private const int MaxRecordsPerMessage = 400;

		private static CustomMessagingManager Cmm => ((Object)(object)NetworkManager.Singleton != (Object)null) ? NetworkManager.Singleton.CustomMessagingManager : null;

		internal static void RegisterHandlers()
		{
			//IL_0027: Unknown result type (might be due to invalid IL or missing references)
			//IL_002c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Expected O, but got Unknown
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0053: Unknown result type (might be due to invalid IL or missing references)
			//IL_0059: Expected O, but got Unknown
			CustomMessagingManager cmm = Cmm;
			if (cmm != null)
			{
				object obj = <>O.<0>__OnAssignReceived;
				if (obj == null)
				{
					HandleNamedMessageDelegate val = OnAssignReceived;
					<>O.<0>__OnAssignReceived = val;
					obj = (object)val;
				}
				cmm.RegisterNamedMessageHandler("LVS_Assign", (HandleNamedMessageDelegate)obj);
				object obj2 = <>O.<1>__OnRequestReceived;
				if (obj2 == null)
				{
					HandleNamedMessageDelegate val2 = OnRequestReceived;
					<>O.<1>__OnRequestReceived = val2;
					obj2 = (object)val2;
				}
				cmm.RegisterNamedMessageHandler("LVS_Request", (HandleNamedMessageDelegate)obj2);
			}
		}

		internal static void BroadcastAssignments(List<ulong> ids)
		{
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton == (Object)null || !singleton.IsServer)
			{
				return;
			}
			List<ulong> list = null;
			foreach (ulong connectedClientsId in singleton.ConnectedClientsIds)
			{
				if (connectedClientsId != 0)
				{
					(list ?? (list = new List<ulong>())).Add(connectedClientsId);
				}
			}
			if (list != null)
			{
				SendAssignments(ids, list);
			}
		}

		internal static void SendFullTable(ulong clientId)
		{
			List<ulong> list = new List<ulong>(VariantRegistry.Table.Keys);
			if (list.Count != 0)
			{
				SendAssignments(list, new List<ulong> { clientId });
			}
		}

		private static void SendAssignments(List<ulong> ids, List<ulong> targetClients)
		{
			//IL_00bb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c1: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00de: Unknown result type (might be due to invalid IL or missing references)
			//IL_010b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			//IL_0129: Unknown result type (might be due to invalid IL or missing references)
			//IL_012f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0147: Unknown result type (might be due to invalid IL or missing references)
			//IL_014d: Unknown result type (might be due to invalid IL or missing references)
			//IL_018c: Unknown result type (might be due to invalid IL or missing references)
			CustomMessagingManager cmm = Cmm;
			if (cmm == null)
			{
				return;
			}
			FastBufferWriter val = default(FastBufferWriter);
			for (int i = 0; i < ids.Count; i += 400)
			{
				int num = Math.Min(400, ids.Count - i);
				List<KeyValuePair<ulong, VariantData>> list = new List<KeyValuePair<ulong, VariantData>>(num);
				int num2 = 5;
				for (int j = 0; j < num; j++)
				{
					if (VariantRegistry.TryGet(ids[i + j], out var data))
					{
						list.Add(new KeyValuePair<ulong, VariantData>(ids[i + j], data));
						num2 += 12;
					}
				}
				if (list.Count == 0)
				{
					continue;
				}
				((FastBufferWriter)(ref val))..ctor(num2 + 16, (Allocator)2, -1);
				try
				{
					byte b = 2;
					((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b, default(ForPrimitives));
					int count = list.Count;
					((FastBufferWriter)(ref val)).WriteValueSafe<int>(ref count, default(ForPrimitives));
					foreach (KeyValuePair<ulong, VariantData> item in list)
					{
						ulong key = item.Key;
						((FastBufferWriter)(ref val)).WriteValueSafe<ulong>(ref key, default(ForPrimitives));
						((FastBufferWriter)(ref val)).WriteValueSafe<short>(ref item.Value.MeshIndex, default(ForPrimitives));
						((FastBufferWriter)(ref val)).WriteValueSafe<short>(ref item.Value.MaterialIndex, default(ForPrimitives));
					}
					foreach (ulong targetClient in targetClients)
					{
						cmm.SendNamedMessage("LVS_Assign", targetClient, val, (NetworkDelivery)4);
					}
				}
				finally
				{
					((FastBufferWriter)(ref val)).Dispose();
				}
			}
		}

		internal static void SendTableRequest()
		{
			//IL_0024: Unknown result type (might be due to invalid IL or missing references)
			//IL_002a: Unknown result type (might be due to invalid IL or missing references)
			//IL_003a: Unknown result type (might be due to invalid IL or missing references)
			CustomMessagingManager cmm = Cmm;
			if (cmm == null)
			{
				return;
			}
			FastBufferWriter val = default(FastBufferWriter);
			((FastBufferWriter)(ref val))..ctor(4, (Allocator)2, -1);
			try
			{
				byte b = 2;
				((FastBufferWriter)(ref val)).WriteValueSafe<byte>(ref b, default(ForPrimitives));
				cmm.SendNamedMessage("LVS_Request", 0uL, val, (NetworkDelivery)2);
			}
			finally
			{
				((FastBufferWriter)(ref val)).Dispose();
			}
		}

		private static void OnAssignReceived(ulong senderClientId, FastBufferReader reader)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_005d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ad: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b3: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				if (senderClientId != 0)
				{
					return;
				}
				byte b = default(byte);
				((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref b, default(ForPrimitives));
				if (b != 2)
				{
					Plugin.Log.LogWarning((object)$"Ignoring LVS_Assign with protocol v{b} (mine: v{(byte)2}). Update the mod on all peers.");
					return;
				}
				int num = default(int);
				((FastBufferReader)(ref reader)).ReadValueSafe<int>(ref num, default(ForPrimitives));
				ulong networkObjectId = default(ulong);
				for (int i = 0; i < num; i++)
				{
					((FastBufferReader)(ref reader)).ReadValueSafe<ulong>(ref networkObjectId, default(ForPrimitives));
					VariantData variantData = new VariantData();
					((FastBufferReader)(ref reader)).ReadValueSafe<short>(ref variantData.MeshIndex, default(ForPrimitives));
					((FastBufferReader)(ref reader)).ReadValueSafe<short>(ref variantData.MaterialIndex, default(ForPrimitives));
					VariantManager.OnAssignmentReceived(networkObjectId, variantData);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Failed reading LVS_Assign: {arg}");
			}
		}

		private static void OnRequestReceived(ulong senderClientId, FastBufferReader reader)
		{
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				NetworkManager singleton = NetworkManager.Singleton;
				if (!((Object)(object)singleton == (Object)null) && singleton.IsServer)
				{
					byte b = default(byte);
					((FastBufferReader)(ref reader)).ReadValueSafe<byte>(ref b, default(ForPrimitives));
					if (b != 2)
					{
						Plugin.Log.LogWarning((object)$"Client {senderClientId} runs protocol v{b} (mine: v{(byte)2}); replying anyway.");
					}
					SendFullTable(senderClientId);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Failed handling LVS_Request: {arg}");
			}
		}
	}
	[BepInPlugin("com.luth.itemvariantsync", "ItemVariantSync", "1.0.0")]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	[BepInDependency(/*Could not decode attribute arguments.*/)]
	public class Plugin : BaseUnityPlugin
	{
		public const string Guid = "com.luth.itemvariantsync";

		public const string Name = "ItemVariantSync";

		public const string Version = "1.0.0";

		private Harmony _harmony;

		private static int _lastPumpFrame = -1;

		private static bool _announcedPump;

		public static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		internal static int UnityUpdates { get; private set; }

		private void Awake()
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0024: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			_harmony = new Harmony("com.luth.itemvariantsync");
			int num = TryPatch(typeof(GrabbableObjectPatches), "item spawn hook");
			int num2 = TryPatch(typeof(LifecyclePatches), "session lifecycle");
			int num3 = TryPatch(typeof(RoundManagerPatches), "vanilla re-roll override");
			int num4 = TryPatch(typeof(SaveLoadPatches), "persistence");
			int num5 = TryPatch(typeof(HudScrapDisplayPatches), "collected-scrap popup mesh");
			int num6 = TryPatch(typeof(TickPatches), "per-frame pump");
			string text = $"hooks: spawn={num} lifecycle={num2} reroll={num3} persistence={num4} popup={num5} tick={num6}";
			if (num > 0 && num2 > 0)
			{
				Log.LogInfo((object)("ItemVariantSync 1.0.0 (build " + BuildStamp() + ") loaded; " + text + "."));
			}
			else
			{
				Log.LogError((object)("ItemVariantSync 1.0.0 in degraded mode (" + text + ") - core hooks missing (game update?), behavior falls back to vanilla."));
			}
		}

		private static string BuildStamp()
		{
			try
			{
				string location = typeof(Plugin).Assembly.Location;
				return string.IsNullOrEmpty(location) ? "unknown" : File.GetLastWriteTime(location).ToString("MM-dd HH:mm:ss");
			}
			catch
			{
				return "unknown";
			}
		}

		private int TryPatch(Type patchClass, string feature)
		{
			try
			{
				int num = _harmony.CreateClassProcessor(patchClass, true).Patch()?.Count ?? 0;
				if (num == 0)
				{
					Log.LogWarning((object)(feature + ": no methods patched - feature inactive."));
				}
				return num;
			}
			catch (Exception arg)
			{
				Log.LogError((object)$"Failed to apply {feature} patches - that feature is disabled, vanilla behavior applies. {arg}");
				return 0;
			}
		}

		internal static void Pump(bool fromHarmony)
		{
			int frameCount = Time.frameCount;
			if (frameCount == _lastPumpFrame)
			{
				return;
			}
			_lastPumpFrame = frameCount;
			if (fromHarmony && !_announcedPump)
			{
				_announcedPump = true;
				Log.LogMessage((object)("Pump driving from " + TickPatches.ChosenTarget + "; BaseUnityPlugin.Update had fired " + $"{UnityUpdates} time(s) by this point."));
			}
			try
			{
				VariantManager.Tick();
				RuntimeIconsBridge.Tick();
			}
			catch (Exception arg)
			{
				Log.LogWarning((object)$"Pump tick failed: {arg}");
			}
		}

		private void Update()
		{
			UnityUpdates++;
			Pump(fromHarmony: false);
		}
	}
	public class VariantData
	{
		public short MeshIndex = -1;

		public short MaterialIndex = -1;

		public bool IsEmpty => MeshIndex < 0 && MaterialIndex < 0;

		public int Signature()
		{
			return ((MeshIndex + 1) * 397) ^ ((MaterialIndex + 1) * 31);
		}
	}
	internal static class VariantRegistry
	{
		internal static readonly Dictionary<ulong, VariantData> Table = new Dictionary<ulong, VariantData>();

		internal static bool TryGet(ulong networkObjectId, out VariantData data)
		{
			return Table.TryGetValue(networkObjectId, out data);
		}

		internal static void Set(ulong networkObjectId, VariantData data)
		{
			Table[networkObjectId] = data;
		}

		internal static void Clear()
		{
			Table.Clear();
		}
	}
	internal static class VariantIdentity
	{
		private const char ZeroWidthSpace = '\u200b';

		private static readonly Dictionary<Item, Dictionary<int, Item>> Clones = new Dictionary<Item, Dictionary<int, Item>>();

		private static readonly Dictionary<Item, Item> BaseOf = new Dictionary<Item, Item>();

		private static readonly Dictionary<Item, int> SuffixCounter = new Dictionary<Item, int>();

		private static readonly HashSet<GrabbableObject> ActiveSwapped = new HashSet<GrabbableObject>();

		private static readonly List<GrabbableObject> DeadScratch = new List<GrabbableObject>(16);

		private static readonly List<(GrabbableObject item, Item clone)> SwappedDuringSave = new List<(GrabbableObject, Item)>();

		internal static Item BaseFor(Item item)
		{
			Item value;
			return ((Object)(object)item != (Object)null && BaseOf.TryGetValue(item, out value)) ? value : item;
		}

		internal static Item GetOrCreate(Item baseItem, int signature, out bool created)
		{
			created = false;
			if (!Clones.TryGetValue(baseItem, out var value))
			{
				value = (Clones[baseItem] = new Dictionary<int, Item>());
			}
			if (value.TryGetValue(signature, out var value2) && (Object)(object)value2 != (Object)null)
			{
				return value2;
			}
			value2 = Object.Instantiate<Item>(baseItem);
			Object.DontDestroyOnLoad((Object)(object)value2);
			((Object)value2).hideFlags = (HideFlags)61;
			if (!SuffixCounter.TryGetValue(baseItem, out var value3))
			{
				value3 = 0;
			}
			value3 = (SuffixCounter[baseItem] = value3 + 1);
			((Object)value2).name = ((Object)baseItem).name + "_LVS" + value3;
			value2.itemName = baseItem.itemName + new string('\u200b', value3);
			value2.itemIcon = baseItem.itemIcon;
			value[signature] = value2;
			BaseOf[value2] = baseItem;
			created = true;
			return value2;
		}

		internal static void NoteSwapped(GrabbableObject item)
		{
			ActiveSwapped.Add(item);
		}

		internal static void OnSessionReset()
		{
			ActiveSwapped.Clear();
			SwappedDuringSave.Clear();
		}

		internal static void SwapBackForSave()
		{
			SwappedDuringSave.Clear();
			if (ActiveSwapped.Count == 0)
			{
				return;
			}
			DeadScratch.Clear();
			foreach (GrabbableObject item in ActiveSwapped)
			{
				Item value;
				if ((Object)(object)item == (Object)null)
				{
					DeadScratch.Add(item);
				}
				else if ((Object)(object)item.itemProperties != (Object)null && BaseOf.TryGetValue(item.itemProperties, out value))
				{
					SwappedDuringSave.Add((item, item.itemProperties));
					item.itemProperties = value;
				}
			}
			foreach (GrabbableObject item2 in DeadScratch)
			{
				ActiveSwapped.Remove(item2);
			}
		}

		internal static void ReswapAfterSave()
		{
			foreach (var (val, val2) in SwappedDuringSave)
			{
				if ((Object)(object)val != (Object)null && (Object)(object)val2 != (Object)null)
				{
					val.itemProperties = val2;
				}
			}
			SwappedDuringSave.Clear();
		}
	}
	internal static class VariantManager
	{
		private struct PendingItem
		{
			public GrabbableObject Item;

			public float Deadline;
		}

		private static readonly List<PendingItem> Pending = new List<PendingItem>(64);

		private static readonly HashSet<ulong> Outbox = new HashSet<ulong>();

		private static bool _requestWanted;

		private static float _nextRequestAllowed;

		private static int _applied;

		private static int _unresolved;

		private static int _eligibleSeen;

		private static int _passTotal;

		private static int _passEligible;

		private static float _reportDue;

		private static readonly List<ulong> FlushScratch = new List<ulong>(128);

		internal static bool InSession => (Object)(object)NetworkManager.Singleton != (Object)null && NetworkManager.Singleton.IsListening;

		internal static bool IsServer
		{
			get
			{
				NetworkManager singleton = NetworkManager.Singleton;
				return (Object)(object)singleton != (Object)null && singleton.IsListening && singleton.IsServer;
			}
		}

		internal static bool IsEligible(GrabbableObject item)
		{
			if ((Object)(object)item == (Object)null || (Object)(object)item.itemProperties == (Object)null)
			{
				return false;
			}
			Item itemProperties = item.itemProperties;
			if (itemProperties.meshVariants != null && itemProperties.meshVariants.Length != 0)
			{
				return true;
			}
			if (itemProperties.materialVariants != null && itemProperties.materialVariants.Length != 0)
			{
				return true;
			}
			return false;
		}

		private static bool TryGetSpawnedNetworkObject(GrabbableObject item, out NetworkObject netObj)
		{
			if (((Component)item).TryGetComponent<NetworkObject>(ref netObj) && netObj.IsSpawned)
			{
				return true;
			}
			netObj = null;
			return false;
		}

		internal static void OnItemStarted(GrabbableObject item)
		{
			if (InSession && IsEligible(item))
			{
				_eligibleSeen++;
				_reportDue = Time.unscaledTime + 3f;
				if (!TryProcess(item))
				{
					Pending.Add(new PendingItem
					{
						Item = item,
						Deadline = Time.unscaledTime + 10f
					});
				}
			}
		}

		private static bool TryProcess(GrabbableObject item)
		{
			if (!TryGetSpawnedNetworkObject(item, out var netObj))
			{
				return false;
			}
			if (IsServer)
			{
				EnsureAssignedServer(item, netObj, broadcast: true);
				return true;
			}
			return ApplyOrRequest(item, netObj);
		}

		internal static void Tick()
		{
			if (Pending.Count > 0)
			{
				PumpPending();
			}
			FlushReport();
			if (_requestWanted && Time.unscaledTime >= _nextRequestAllowed)
			{
				SendRequestNow();
			}
			if (Outbox.Count > 0)
			{
				FlushOutbox();
			}
		}

		private static void PumpPending()
		{
			for (int num = Pending.Count - 1; num >= 0; num--)
			{
				PendingItem pendingItem = Pending[num];
				bool flag = (Object)(object)pendingItem.Item == (Object)null;
				bool flag2 = !flag && TryProcess(pendingItem.Item);
				bool flag3 = !flag && !flag2 && Time.unscaledTime > pendingItem.Deadline;
				if (flag || flag2 || flag3)
				{
					if (flag3)
					{
						_unresolved++;
						_reportDue = Time.unscaledTime + 2f;
					}
					int index = Pending.Count - 1;
					Pending[num] = Pending[index];
					Pending.RemoveAt(index);
				}
			}
		}

		internal static void NoteScrapPass(int total, int eligible)
		{
			_passTotal += total;
			_passEligible += eligible;
			_reportDue = Time.unscaledTime + 3f;
		}

		private static void FlushReport()
		{
			if (_reportDue <= 0f || Time.unscaledTime < _reportDue)
			{
				return;
			}
			_reportDue = 0f;
			if (_passEligible == 0 && _applied == 0 && _unresolved == 0)
			{
				if (_passTotal > 0)
				{
					Plugin.Log.LogMessage((object)($"{_passTotal} scrap item(s) synced, none of them use vanilla's variant arrays - " + "nothing for this mod to sync here."));
				}
				Reset();
				return;
			}
			if (_unresolved > 0)
			{
				Plugin.Log.LogWarning((object)($"{_applied} item(s) took the host's variant, {_unresolved} never received one and keep " + "whatever this client rolled locally - those will not match the host. The host either has no assignment for them or the table did not arrive in time."));
			}
			else if (_applied > 0)
			{
				Plugin.Log.LogMessage((object)($"{_applied} of {((_passEligible > _eligibleSeen) ? _passEligible : _eligibleSeen)} " + "eligible item(s) took the host's variant; none outstanding."));
			}
			Reset();
		}

		private static void Reset()
		{
			_applied = (_unresolved = (_eligibleSeen = 0));
			_passTotal = (_passEligible = 0);
		}

		internal static void EnsureAssignedServer(GrabbableObject item, NetworkObject netObj, bool broadcast)
		{
			ulong networkObjectId = netObj.NetworkObjectId;
			if (!VariantRegistry.TryGet(networkObjectId, out var data))
			{
				data = Roll(item);
				if (data.IsEmpty)
				{
					return;
				}
				VariantRegistry.Set(networkObjectId, data);
			}
			Apply(item, data);
			if (broadcast)
			{
				Outbox.Add(networkObjectId);
			}
		}

		private static bool ApplyOrRequest(GrabbableObject item, NetworkObject netObj)
		{
			if (VariantRegistry.TryGet(netObj.NetworkObjectId, out var data))
			{
				Apply(item, data);
				_applied++;
				_reportDue = Time.unscaledTime + 2f;
				return true;
			}
			_requestWanted = true;
			return false;
		}

		private static void SendRequestNow()
		{
			_requestWanted = false;
			if (InSession && !IsServer)
			{
				_nextRequestAllowed = Time.unscaledTime + 2f;
				Messaging.SendTableRequest();
			}
		}

		internal static void OnAssignmentReceived(ulong networkObjectId, VariantData data)
		{
			VariantRegistry.Set(networkObjectId, data);
			NetworkManager singleton = NetworkManager.Singleton;
			if ((Object)(object)singleton != (Object)null && singleton.SpawnManager != null && singleton.SpawnManager.SpawnedObjects.TryGetValue(networkObjectId, out var value) && (Object)(object)value != (Object)null)
			{
				GrabbableObject component = ((Component)value).GetComponent<GrabbableObject>();
				if ((Object)(object)component != (Object)null)
				{
					Apply(component, data);
				}
			}
		}

		internal static void ReapplyIfKnown(GrabbableObject item)
		{
			if (!((Object)(object)item == (Object)null) && TryGetSpawnedNetworkObject(item, out var netObj))
			{
				if (VariantRegistry.TryGet(netObj.NetworkObjectId, out var data))
				{
					Apply(item, data);
					return;
				}
				_requestWanted = true;
				Pending.Add(new PendingItem
				{
					Item = item,
					Deadline = Time.unscaledTime + 10f
				});
			}
		}

		private static VariantData Roll(GrabbableObject item)
		{
			Item itemProperties = item.itemProperties;
			VariantData variantData = new VariantData();
			if (itemProperties.meshVariants != null && itemProperties.meshVariants.Length != 0)
			{
				variantData.MeshIndex = (short)Random.Range(0, itemProperties.meshVariants.Length);
			}
			if (itemProperties.materialVariants != null && itemProperties.materialVariants.Length != 0)
			{
				variantData.MaterialIndex = (short)Random.Range(0, itemProperties.materialVariants.Length);
			}
			return variantData;
		}

		internal static void Apply(GrabbableObject item, VariantData data)
		{
			if ((Object)(object)item == (Object)null || data == null)
			{
				return;
			}
			Item itemProperties = item.itemProperties;
			try
			{
				if ((Object)(object)itemProperties != (Object)null && data.MeshIndex >= 0 && itemProperties.meshVariants != null && data.MeshIndex < itemProperties.meshVariants.Length && (Object)(object)itemProperties.meshVariants[data.MeshIndex] != (Object)null)
				{
					MeshFilter componentInChildren = default(MeshFilter);
					if (!((Component)item).TryGetComponent<MeshFilter>(ref componentInChildren))
					{
						componentInChildren = ((Component)item).gameObject.GetComponentInChildren<MeshFilter>();
					}
					if ((Object)(object)componentInChildren != (Object)null)
					{
						componentInChildren.mesh = itemProperties.meshVariants[data.MeshIndex];
					}
				}
				if ((Object)(object)itemProperties != (Object)null && data.MaterialIndex >= 0 && itemProperties.materialVariants != null && data.MaterialIndex < itemProperties.materialVariants.Length && (Object)(object)itemProperties.materialVariants[data.MaterialIndex] != (Object)null)
				{
					MeshRenderer componentInChildren2 = default(MeshRenderer);
					if (!((Component)item).TryGetComponent<MeshRenderer>(ref componentInChildren2))
					{
						componentInChildren2 = ((Component)item).gameObject.GetComponentInChildren<MeshRenderer>();
					}
					if ((Object)(object)componentInChildren2 != (Object)null)
					{
						((Renderer)componentInChildren2).sharedMaterial = itemProperties.materialVariants[data.MaterialIndex];
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Failed applying mesh/material variant to '{((Object)((Component)item).gameObject).name}': {arg}");
			}
			if (!((Object)(object)item.itemProperties != (Object)null))
			{
				return;
			}
			try
			{
				Item baseItem = VariantIdentity.BaseFor(item.itemProperties);
				bool created;
				Item orCreate = VariantIdentity.GetOrCreate(baseItem, data.Signature(), out created);
				if (item.itemProperties != orCreate)
				{
					item.itemProperties = orCreate;
				}
				VariantIdentity.NoteSwapped(item);
				RuntimeIconsBridge.OnCloneAssigned(item, orCreate, baseItem, created);
			}
			catch (Exception arg2)
			{
				Plugin.Log.LogWarning((object)$"Identity swap failed for '{((Object)((Component)item).gameObject).name}': {arg2}");
			}
		}

		internal static void QueueBroadcast(ulong networkObjectId)
		{
			Outbox.Add(networkObjectId);
		}

		private static void FlushOutbox()
		{
			if (!IsServer)
			{
				Outbox.Clear();
				return;
			}
			try
			{
				FlushScratch.Clear();
				foreach (ulong item in Outbox)
				{
					FlushScratch.Add(item);
				}
				Messaging.BroadcastAssignments(FlushScratch);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Failed broadcasting variant assignments: {arg}");
			}
			Outbox.Clear();
		}

		internal static void ResetSession()
		{
			VariantRegistry.Clear();
			Outbox.Clear();
			Pending.Clear();
			VariantIdentity.OnSessionReset();
			_requestWanted = false;
			_nextRequestAllowed = 0f;
			Reset();
			_reportDue = 0f;
		}
	}
}
namespace ItemVariantSync.Patches
{
	[HarmonyPatch(typeof(GrabbableObject))]
	internal static class GrabbableObjectPatches
	{
		[HarmonyPatch("Start")]
		[HarmonyPostfix]
		private static void Start_Postfix(GrabbableObject __instance)
		{
			try
			{
				VariantManager.OnItemStarted(__instance);
			}
			catch (Exception arg)
			{
				ManualLogSource log = Plugin.Log;
				object arg2;
				if (__instance == null)
				{
					arg2 = null;
				}
				else
				{
					GameObject gameObject = ((Component)__instance).gameObject;
					arg2 = ((gameObject != null) ? ((Object)gameObject).name : null);
				}
				log.LogWarning((object)$"GrabbableObject.Start postfix failed for '{arg2}': {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(HUDManager))]
	internal static class HudScrapDisplayPatches
	{
		internal struct DisplayCapture
		{
			public GrabbableObject Source;

			public int BoxIndex;

			public int PrevChildCount;
		}

		[HarmonyPatch("DisplayNewScrapFound")]
		[HarmonyPrefix]
		private static void Prefix(HUDManager __instance, List<GrabbableObject> ___itemsToBeDisplayed, int ___nextBoxIndex, out DisplayCapture __state)
		{
			__state = default(DisplayCapture);
			__state.BoxIndex = -1;
			try
			{
				if (___itemsToBeDisplayed != null && ___itemsToBeDisplayed.Count != 0 && __instance.ScrapItemBoxes != null && ___nextBoxIndex >= 0 && ___nextBoxIndex < __instance.ScrapItemBoxes.Length)
				{
					__state.Source = ___itemsToBeDisplayed[0];
					__state.BoxIndex = ___nextBoxIndex;
					Transform itemObjectContainer = __instance.ScrapItemBoxes[___nextBoxIndex].itemObjectContainer;
					__state.PrevChildCount = (((Object)(object)itemObjectContainer != (Object)null) ? itemObjectContainer.childCount : (-1));
				}
			}
			catch
			{
				__state.BoxIndex = -1;
			}
		}

		[HarmonyPatch("DisplayNewScrapFound")]
		[HarmonyPostfix]
		private static void Postfix(HUDManager __instance, DisplayCapture __state)
		{
			try
			{
				if ((Object)(object)__state.Source == (Object)null || __state.BoxIndex < 0)
				{
					return;
				}
				Transform itemObjectContainer = __instance.ScrapItemBoxes[__state.BoxIndex].itemObjectContainer;
				if ((Object)(object)itemObjectContainer == (Object)null || itemObjectContainer.childCount == 0 || (__state.PrevChildCount >= 0 && itemObjectContainer.childCount <= __state.PrevChildCount))
				{
					return;
				}
				MeshFilter val = ResolveFilter(((Component)__state.Source).gameObject);
				if (!((Object)(object)val == (Object)null) && !((Object)(object)val.sharedMesh == (Object)null))
				{
					MeshFilter val2 = ResolveFilter(((Component)itemObjectContainer.GetChild(itemObjectContainer.childCount - 1)).gameObject);
					if ((Object)(object)val2 != (Object)null && (Object)(object)val2.sharedMesh != (Object)(object)val.sharedMesh)
					{
						val2.mesh = val.sharedMesh;
					}
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Collected-scrap popup mesh fix failed: {arg}");
			}
		}

		private static MeshFilter ResolveFilter(GameObject go)
		{
			MeshFilter result = default(MeshFilter);
			if (go.TryGetComponent<MeshFilter>(ref result))
			{
				return result;
			}
			return go.GetComponentInChildren<MeshFilter>();
		}
	}
	internal static class LifecyclePatches
	{
		private static bool _loggedCapableItems;

		[HarmonyPatch(typeof(StartOfRound), "Awake")]
		[HarmonyPostfix]
		private static void StartOfRound_Awake_Postfix()
		{
			try
			{
				bool inSession = VariantManager.InSession;
				bool isServer = VariantManager.IsServer;
				Plugin.Log.LogMessage((object)($"StartOfRound.Awake: InSession={inSession} IsServer={isServer} " + "(" + ((!inSession) ? "no session yet - variants cannot sync from here" : (isServer ? "host, assigns authoritatively" : "client, will pull the host's table")) + ")"));
				LogVariantCapableItemsOnce();
				DawnLibBridge.TryInit();
				VariantManager.ResetSession();
				Messaging.RegisterHandlers();
				if (inSession && !isServer)
				{
					Messaging.SendTableRequest();
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"StartOfRound.Awake postfix failed: {arg}");
			}
		}

		private static void LogVariantCapableItemsOnce()
		{
			if (_loggedCapableItems)
			{
				return;
			}
			_loggedCapableItems = true;
			List<Item> list = StartOfRound.Instance?.allItemsList?.itemsList;
			if (list == null)
			{
				Plugin.Log.LogWarning((object)"Item pool unavailable - cannot report which items are variant-capable.");
				return;
			}
			List<string> list2 = new List<string>();
			foreach (Item item in list)
			{
				if (!((Object)(object)item == (Object)null))
				{
					bool flag = item.meshVariants != null && item.meshVariants.Length != 0;
					bool flag2 = item.materialVariants != null && item.materialVariants.Length != 0;
					if (flag || flag2)
					{
						list2.Add(item.itemName + "(" + (flag ? "M" : "") + (flag2 ? "m" : "") + ")");
					}
				}
			}
			if (list2.Count == 0)
			{
				Plugin.Log.LogWarning((object)($"None of the {list.Count} loaded items carry vanilla variant arrays - this mod has nothing " + "to act on in this modpack. M=meshVariants, m=materialVariants."));
			}
			else
			{
				Plugin.Log.LogMessage((object)($"{list2.Count} of {list.Count} items are variant-capable (M=mesh, m=material): " + string.Join(", ", list2.ToArray())));
			}
		}

		[HarmonyPatch(typeof(GameNetworkManager), "Disconnect", new Type[] { })]
		[HarmonyPostfix]
		private static void Disconnect_Postfix()
		{
			VariantManager.ResetSession();
		}
	}
	[HarmonyPatch(typeof(RoundManager))]
	internal static class RoundManagerPatches
	{
		private static int _lastPassFrame = -1;

		private static NetworkObjectReference[] _lastPassArray;

		[HarmonyPatch("SyncScrapValuesClientRpc")]
		[HarmonyPostfix]
		private static void SyncScrapValues_Postfix(NetworkObjectReference[] spawnedScrap)
		{
			if (spawnedScrap == null || !VariantManager.InSession || (Time.frameCount == _lastPassFrame && spawnedScrap == _lastPassArray))
			{
				return;
			}
			_lastPassFrame = Time.frameCount;
			_lastPassArray = spawnedScrap;
			bool isServer = VariantManager.IsServer;
			int num = 0;
			NetworkObject val = default(NetworkObject);
			for (int i = 0; i < spawnedScrap.Length; i++)
			{
				try
				{
					if (!((NetworkObjectReference)(ref spawnedScrap[i])).TryGet(ref val, (NetworkManager)null) || (Object)(object)val == (Object)null)
					{
						continue;
					}
					GrabbableObject component = ((Component)val).GetComponent<GrabbableObject>();
					if (!((Object)(object)component == (Object)null) && VariantManager.IsEligible(component))
					{
						num++;
						if (isServer)
						{
							VariantManager.EnsureAssignedServer(component, val, broadcast: true);
						}
						else
						{
							VariantManager.ReapplyIfKnown(component);
						}
					}
				}
				catch (Exception arg)
				{
					Plugin.Log.LogWarning((object)$"SyncScrapValues postfix failed for entry {i}: {arg}");
				}
			}
			VariantManager.NoteScrapPass(spawnedScrap.Length, num);
		}
	}
	internal static class SaveLoadPatches
	{
		[HarmonyPatch(typeof(GameNetworkManager), "SaveItemsInShip")]
		[HarmonyPrefix]
		[HarmonyPriority(800)]
		private static void SaveItemsInShip_Prefix()
		{
			try
			{
				VariantIdentity.SwapBackForSave();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Identity swap-back before save failed: {arg}");
			}
		}

		[HarmonyPatch(typeof(GameNetworkManager), "SaveItemsInShip")]
		[HarmonyFinalizer]
		private static void SaveItemsInShip_Finalizer()
		{
			try
			{
				VariantIdentity.ReswapAfterSave();
			}
			catch (Exception arg)
			{
				Plugin.Log.LogWarning((object)$"Identity re-swap after save failed: {arg}");
			}
		}
	}
	internal static class TickPatches
	{
		private static readonly Type[] Candidates = new Type[3]
		{
			typeof(StartOfRound),
			typeof(HUDManager),
			typeof(RoundManager)
		};

		internal static string ChosenTarget { get; private set; } = "none";

		[HarmonyTargetMethods]
		private static IEnumerable<MethodBase> TargetMethods()
		{
			Type[] candidates = Candidates;
			foreach (Type type in candidates)
			{
				MethodInfo method = AccessTools.DeclaredMethod(type, "Update", (Type[])null, (Type[])null);
				if (!(method == null))
				{
					ChosenTarget = type.Name + ".Update";
					yield return method;
					yield break;
				}
			}
			Plugin.Log.LogError((object)"No declared Update found on StartOfRound, HUDManager or RoundManager - the per-frame pump has no driver, so variant sync will not complete. Game update?");
		}

		[HarmonyPostfix]
		private static void Update_Postfix()
		{
			Plugin.Pump(fromHarmony: true);
		}
	}
}