Decompiled source of SharedDiscovery v0.1.0

plugins\SharedDiscovery.Core.dll

Decompiled a month ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using Microsoft.CodeAnalysis;
using Newtonsoft.Json;
using SharedDiscovery.Core.Discovery;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("SharedDiscovery.Core")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+a98234877f9b3ac258bc4c93ecb7a728180ef2ef")]
[assembly: AssemblyProduct("SharedDiscovery.Core")]
[assembly: AssemblyTitle("SharedDiscovery.Core")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace SharedDiscovery.Core.Persistence
{
	public sealed class WorldDiscoveryData
	{
		public int Version { get; set; } = 1;

		public List<string> Discoveries { get; set; } = new List<string>();
	}
	public sealed class WorldDiscoveryStore
	{
		private const int CurrentVersion = 1;

		private readonly string _worldsDirectory;

		private readonly Action<string> _info;

		private readonly Action<string> _warning;

		public WorldDiscoveryStore(string worldsDirectory, Action<string>? info = null, Action<string>? warning = null)
		{
			_worldsDirectory = worldsDirectory ?? throw new ArgumentNullException("worldsDirectory");
			_info = info ?? ((Action<string>)delegate
			{
			});
			_warning = warning ?? ((Action<string>)delegate
			{
			});
		}

		public IReadOnlyList<string> Load(string worldId)
		{
			//IL_00fd: Expected O, but got Unknown
			string path = GetPath(worldId);
			try
			{
				if (!File.Exists(path))
				{
					_info("No SharedDiscovery state exists yet for world '" + worldId + "'.");
					return Array.Empty<string>();
				}
				string text = File.ReadAllText(path);
				if (string.IsNullOrWhiteSpace(text))
				{
					_warning("SharedDiscovery state for world '" + worldId + "' is empty; starting with no discoveries.");
					return Array.Empty<string>();
				}
				WorldDiscoveryData worldDiscoveryData = JsonConvert.DeserializeObject<WorldDiscoveryData>(text);
				if (worldDiscoveryData?.Discoveries == null)
				{
					_warning("SharedDiscovery state for world '" + worldId + "' did not contain discoveries; starting empty.");
					return Array.Empty<string>();
				}
				return worldDiscoveryData.Discoveries.Where(DiscoveryValidator.IsValidItemId).Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string value) => value, StringComparer.Ordinal)
					.ToArray();
			}
			catch (JsonException ex)
			{
				JsonException ex2 = ex;
				_warning("Could not parse SharedDiscovery state for world '" + worldId + "': " + ((Exception)(object)ex2).Message);
				return Array.Empty<string>();
			}
			catch (IOException ex3)
			{
				_warning("Could not read SharedDiscovery state for world '" + worldId + "': " + ex3.Message);
				return Array.Empty<string>();
			}
			catch (UnauthorizedAccessException ex4)
			{
				_warning("Could not access SharedDiscovery state for world '" + worldId + "': " + ex4.Message);
				return Array.Empty<string>();
			}
		}

		public bool Save(string worldId, IEnumerable<string> discoveries)
		{
			string path = GetPath(worldId);
			try
			{
				Directory.CreateDirectory(_worldsDirectory);
				string contents = JsonConvert.SerializeObject((object)new WorldDiscoveryData
				{
					Version = 1,
					Discoveries = discoveries.Where(DiscoveryValidator.IsValidItemId).Distinct<string>(StringComparer.Ordinal).OrderBy<string, string>((string value) => value, StringComparer.Ordinal)
						.ToList()
				}, (Formatting)1);
				string text = path + ".tmp";
				File.WriteAllText(text, contents);
				if (File.Exists(path))
				{
					File.Delete(path);
				}
				File.Move(text, path);
				return true;
			}
			catch (IOException ex)
			{
				_warning("Could not save SharedDiscovery state for world '" + worldId + "': " + ex.Message);
				return false;
			}
			catch (UnauthorizedAccessException ex2)
			{
				_warning("Could not access SharedDiscovery state for world '" + worldId + "': " + ex2.Message);
				return false;
			}
		}

		private string GetPath(string worldId)
		{
			string text = (DiscoveryValidator.IsValidItemId(worldId) ? worldId : "unknown-world");
			return Path.Combine(_worldsDirectory, text + ".json");
		}
	}
}
namespace SharedDiscovery.Core.Network
{
	public sealed class DiscoverySnapshot
	{
		public IReadOnlyList<string> ItemIds { get; }

		public DiscoverySnapshot(IEnumerable<string> itemIds)
		{
			ItemIds = (itemIds ?? Enumerable.Empty<string>()).Where(DiscoveryValidator.IsValidItemId).Distinct().OrderBy<string, string>((string value) => value, StringComparer.Ordinal)
				.ToArray();
		}
	}
}
namespace SharedDiscovery.Core.Discovery
{
	public static class DiscoveryOriginPolicy
	{
		public static bool ShouldApplyToHost(long sender, bool isNewDiscovery)
		{
			return sender != 0 && isNewDiscovery;
		}
	}
	public sealed class DiscoveryRegistry
	{
		private readonly HashSet<string> _discoveries;

		public int Count => _discoveries.Count;

		public DiscoveryRegistry()
		{
			_discoveries = new HashSet<string>(StringComparer.Ordinal);
		}

		public bool Add(string itemId)
		{
			if (!DiscoveryValidator.IsValidItemId(itemId))
			{
				return false;
			}
			return _discoveries.Add(itemId);
		}

		public bool Contains(string itemId)
		{
			if (DiscoveryValidator.IsValidItemId(itemId))
			{
				return _discoveries.Contains(itemId);
			}
			return false;
		}

		public void ReplaceWith(IEnumerable<string> itemIds)
		{
			_discoveries.Clear();
			foreach (string item in itemIds ?? Enumerable.Empty<string>())
			{
				Add(item);
			}
		}

		public void Clear()
		{
			_discoveries.Clear();
		}

		public IReadOnlyList<string> Snapshot()
		{
			return _discoveries.OrderBy<string, string>((string value) => value, StringComparer.Ordinal).ToArray();
		}
	}
	public static class DiscoveryValidator
	{
		public const int MaxItemIdLength = 128;

		public static bool IsValidItemId(string? itemId)
		{
			if (itemId == null || itemId.Trim().Length == 0)
			{
				return false;
			}
			if (itemId.Length > 128)
			{
				return false;
			}
			foreach (char c in itemId)
			{
				if (!char.IsLetterOrDigit(c) && c != '_' && c != '-' && c != '.')
				{
					return false;
				}
			}
			return true;
		}
	}
}

plugins\SharedDiscovery.dll

Decompiled a month ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using BepInEx;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using SharedDiscovery.Config;
using SharedDiscovery.Core.Discovery;
using SharedDiscovery.Core.Persistence;
using SharedDiscovery.Discovery;
using SharedDiscovery.Network;
using SharedDiscovery.Persistence;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.6.2", FrameworkDisplayName = ".NET Framework 4.6.2")]
[assembly: AssemblyCompany("SharedDiscovery")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+a98234877f9b3ac258bc4c93ecb7a728180ef2ef")]
[assembly: AssemblyProduct("SharedDiscovery")]
[assembly: AssemblyTitle("SharedDiscovery")]
[assembly: AssemblyVersion("1.0.0.0")]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
}
namespace SharedDiscovery
{
	[BepInPlugin("lorenzo.valheim.shareddiscovery", "SharedDiscovery", "0.1.0")]
	public sealed class Plugin : BaseUnityPlugin
	{
		public const string Guid = "lorenzo.valheim.shareddiscovery";

		public const string Name = "SharedDiscovery";

		public const string Version = "0.1.0";

		private Harmony? _harmony;

		internal static Plugin Instance { get; private set; }

		internal static ManualLogSource Log { get; private set; }

		internal static DiscoveryService Discovery { get; private set; }

		internal static VanillaDiscoveryService VanillaDiscovery { get; private set; }

		internal static DiscoveryNetwork Network { get; private set; }

		internal static DiscoverySessionState Session { get; private set; }

		private void Awake()
		{
			//IL_0059: Unknown result type (might be due to invalid IL or missing references)
			//IL_0063: Expected O, but got Unknown
			Instance = this;
			Log = ((BaseUnityPlugin)this).Logger;
			ModConfig.Bind(((BaseUnityPlugin)this).Config);
			WorldDiscoveryStoreAdapter store = new WorldDiscoveryStoreAdapter();
			Session = new DiscoverySessionState();
			Discovery = new DiscoveryService(store);
			VanillaDiscovery = new VanillaDiscoveryService();
			Network = new DiscoveryNetwork(Discovery, VanillaDiscovery);
			_harmony = new Harmony("lorenzo.valheim.shareddiscovery");
			_harmony.PatchAll();
			Network.TryRegister();
			Log.LogInfo((object)"SharedDiscovery 0.1.0 loaded.");
		}

		private void OnDestroy()
		{
			Harmony? harmony = _harmony;
			if (harmony != null)
			{
				harmony.UnpatchSelf();
			}
			ResetSessionState(save: true);
			Log.LogInfo((object)"SharedDiscovery unloaded.");
		}

		internal static void ResetSessionState(bool save)
		{
			if (save)
			{
				Discovery.SaveCurrentWorld();
			}
			Session.Reset();
			Discovery.Clear();
			VanillaDiscovery.ClearPending();
			Network.ResetSession();
		}

		internal static void DebugLog(string message)
		{
			if (ModConfig.DebugLogging.Value)
			{
				Log.LogInfo((object)message);
			}
		}
	}
}
namespace SharedDiscovery.Persistence
{
	internal sealed class WorldDiscoveryStoreAdapter
	{
		private readonly WorldDiscoveryStore _store;

		public WorldDiscoveryStoreAdapter()
		{
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0047: Expected O, but got Unknown
			string text = Path.Combine(Paths.ConfigPath, "SharedDiscovery", "worlds");
			_store = new WorldDiscoveryStore(text, (Action<string>)Plugin.Log.LogInfo, (Action<string>)Plugin.Log.LogWarning);
		}

		public IReadOnlyList<string> Load(string worldId)
		{
			return _store.Load(worldId);
		}

		public bool Save(string worldId, IEnumerable<string> discoveries)
		{
			return _store.Save(worldId, discoveries);
		}
	}
}
namespace SharedDiscovery.Patches
{
	[HarmonyPatch(typeof(Player), "AddKnownItem")]
	internal static class KnownItemPatch
	{
		private static void Prefix(Player __instance, ItemData item, ref bool __state)
		{
			__state = true;
			if (ModConfig.Enabled.Value && !((Object)(object)__instance == (Object)null) && item?.m_shared != null)
			{
				__state = __instance.IsMaterialKnown(item.m_shared.m_name);
			}
		}

		private static void Postfix(Player __instance, ItemData item, bool __state)
		{
			if (!ModConfig.Enabled.Value || __state)
			{
				return;
			}
			if ((Object)(object)__instance == (Object)null || (Object)(object)__instance != (Object)(object)Player.m_localPlayer || item?.m_shared == null)
			{
				Plugin.DebugLog("Ignoring local AddKnownItem '" + (item?.m_shared?.m_name ?? "<unknown>") + "': not local player.");
				return;
			}
			if (VanillaDiscoveryService.IsApplyingSharedDiscovery)
			{
				Plugin.DebugLog("Ignoring local AddKnownItem '" + item.m_shared.m_name + "': remote-application guard active.");
				return;
			}
			if (!__instance.IsMaterialKnown(item.m_shared.m_name))
			{
				Plugin.DebugLog("Ignoring local AddKnownItem '" + item.m_shared.m_name + "': vanilla did not retain knowledge.");
				return;
			}
			if (!Plugin.Session.AcceptLocalDiscoveries)
			{
				Plugin.DebugLog("Ignoring local AddKnownItem '" + item.m_shared.m_name + "': local discoveries not accepted.");
				return;
			}
			if (!Plugin.Session.IsLocalPlayerReady)
			{
				Plugin.DebugLog("Ignoring local AddKnownItem '" + item.m_shared.m_name + "': session not ready.");
				return;
			}
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer() && !Plugin.Discovery.IsWorldLoaded)
			{
				Plugin.DebugLog("Ignoring local AddKnownItem '" + item.m_shared.m_name + "': no active world state.");
				return;
			}
			try
			{
				string candidatePrefabName = VanillaItemIdentifier.GetCandidatePrefabName(item);
				Plugin.DebugLog("Discovery candidate: sharedName='" + item.m_shared.m_name + "' dropPrefab='" + (candidatePrefabName ?? "<none>") + "'.");
				string prefabItemId = VanillaItemIdentifier.GetPrefabItemId(item);
				if (prefabItemId == null)
				{
					Plugin.DebugLog("Failed to resolve stable prefab ID: sharedName='" + item.m_shared.m_name + "' dropPrefab='" + (candidatePrefabName ?? "<none>") + "'.");
				}
				else if (Plugin.Session.IsRestoredItem(prefabItemId))
				{
					Plugin.DebugLog("Ignoring restored startup item '" + prefabItemId + "'.");
				}
				else
				{
					Plugin.DebugLog(prefabItemId + " is not part of restored startup baseline; processing discovery.");
					Plugin.DebugLog("Resolved stable prefab ID: " + prefabItemId);
					Plugin.Network.SubmitLocalDiscovery(prefabItemId);
				}
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"SharedDiscovery failed to process discovered item '{item.m_shared.m_name}': {arg}");
			}
		}
	}
	[HarmonyPatch(typeof(ZNet), "Awake")]
	internal static class ZNetAwakePatch
	{
		private static void Postfix()
		{
			Plugin.Network.TryRegister();
		}
	}
	[HarmonyPatch(typeof(ZNet), "OnDestroy")]
	internal static class ZNetDestroyPatch
	{
		private static void Prefix()
		{
			Plugin.ResetSessionState(save: true);
		}
	}
	[HarmonyPatch(typeof(Player), "OnSpawned")]
	internal static class PlayerSpawnedPatch
	{
		private static void Postfix(Player __instance)
		{
			if (ModConfig.Enabled.Value && !((Object)(object)__instance == (Object)null) && !((Object)(object)__instance != (Object)(object)Player.m_localPlayer))
			{
				Plugin.Network.TryRegister();
				Plugin.Session.CaptureInitialRestoredItems(__instance);
				if (((Character)__instance).InIntro())
				{
					Plugin.DebugLog("Local player spawned in intro; waiting for Valkyrie.DropPlayer before requesting snapshot.");
				}
				else
				{
					MarkPlayerReady(__instance);
				}
			}
		}

		internal static void MarkPlayerReady(Player player)
		{
			if (Plugin.Session.MarkLocalPlayerReady(player))
			{
				Plugin.Network.RequestSnapshot();
			}
		}
	}
}
namespace SharedDiscovery.Network
{
	internal static class DiscoveryMessages
	{
		public const string SubmitDiscovery = "SharedDiscovery_SubmitDiscovery";

		public const string ApplyDiscovery = "SharedDiscovery_ApplyDiscovery";

		public const string RequestSnapshot = "SharedDiscovery_RequestSnapshot";

		public const string Snapshot = "SharedDiscovery_Snapshot";
	}
	internal sealed class DiscoveryNetwork
	{
		private readonly DiscoveryService _discovery;

		private readonly VanillaDiscoveryService _vanillaDiscovery;

		private bool _registered;

		private int _sessionGeneration;

		public DiscoveryNetwork(DiscoveryService discovery, VanillaDiscoveryService vanillaDiscovery)
		{
			_discovery = discovery;
			_vanillaDiscovery = vanillaDiscovery;
		}

		public void TryRegister()
		{
			if (!_registered && ZRoutedRpc.instance != null)
			{
				ZRoutedRpc.instance.Register<string>("SharedDiscovery_SubmitDiscovery", (Action<long, string>)RPC_SubmitDiscovery);
				ZRoutedRpc.instance.Register<string>("SharedDiscovery_ApplyDiscovery", (Action<long, string>)RPC_ApplyDiscovery);
				ZRoutedRpc.instance.Register("SharedDiscovery_RequestSnapshot", (Action<long>)RPC_RequestSnapshot);
				ZRoutedRpc.instance.Register<ZPackage>("SharedDiscovery_Snapshot", (Action<long, ZPackage>)RPC_Snapshot);
				_registered = true;
				Plugin.DebugLog($"Registered SharedDiscovery routed RPC handlers. Session network generation: {_sessionGeneration}.");
			}
		}

		public void ResetSession()
		{
			_registered = false;
			_sessionGeneration++;
			Plugin.DebugLog($"Session network generation: {_sessionGeneration}.");
		}

		public void SubmitLocalDiscovery(string itemId)
		{
			if (!DiscoveryValidator.IsValidItemId(itemId) || ZRoutedRpc.instance == null)
			{
				return;
			}
			if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
			{
				Plugin.DebugLog("Sending discovery '" + itemId + "' to server.");
				if (_discovery.TryAddServerDiscovery(itemId))
				{
					BroadcastDiscovery(itemId);
				}
			}
			else
			{
				Plugin.DebugLog("Sending discovery '" + itemId + "' to server.");
				ZRoutedRpc.instance.InvokeRoutedRPC("SharedDiscovery_SubmitDiscovery", new object[1] { itemId });
			}
		}

		public void RequestSnapshot()
		{
			if (ZRoutedRpc.instance != null && WorldContext.TryGetCurrentWorldId(out string worldId))
			{
				if ((Object)(object)ZNet.instance != (Object)null && ZNet.instance.IsServer())
				{
					Plugin.DebugLog("Snapshot request handled locally for world UID " + worldId + ".");
					SendSnapshot(0L);
				}
				else
				{
					Plugin.DebugLog("Snapshot request sent for world UID " + worldId + ".");
					ZRoutedRpc.instance.InvokeRoutedRPC("SharedDiscovery_RequestSnapshot", new object[0]);
				}
			}
		}

		public void SendSnapshot(long peerId)
		{
			//IL_0070: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Expected O, but got Unknown
			if (ZRoutedRpc.instance != null && !((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer() && _discovery.EnsureWorldLoaded())
			{
				ZPackage val = WriteSnapshot(_discovery.Snapshot);
				Plugin.DebugLog($"Sending snapshot with {_discovery.Snapshot.Count} discovery ID(s).");
				if (peerId == 0L)
				{
					RPC_Snapshot(0L, new ZPackage(val.GetArray()));
					return;
				}
				Plugin.DebugLog($"Snapshot response sent to peer {peerId}.");
				ZRoutedRpc.instance.InvokeRoutedRPC(peerId, "SharedDiscovery_Snapshot", new object[1] { val });
			}
		}

		private void RPC_SubmitDiscovery(long sender, string itemId)
		{
			Plugin.DebugLog($"Client discovery '{itemId}' received from peer {sender}.");
			if ((Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return;
			}
			if (!IsValidClientSender(sender))
			{
				Plugin.Log.LogWarning((object)$"Rejected discovery from unknown peer {sender}.");
			}
			else if (!DiscoveryValidator.IsValidItemId(itemId))
			{
				Plugin.Log.LogWarning((object)$"Rejected invalid discovery from peer {sender}.");
			}
			else if (!VanillaItemExists(itemId))
			{
				Plugin.Log.LogWarning((object)$"Rejected discovery for missing item prefab from peer {sender}: {itemId}");
			}
			else if (_discovery.TryAddServerDiscovery(itemId))
			{
				if (DiscoveryOriginPolicy.ShouldApplyToHost(sender, true))
				{
					Plugin.DebugLog("Applying client-originated discovery '" + itemId + "' to host local player.");
					_vanillaDiscovery.ApplyOrQueue(itemId);
				}
				BroadcastDiscovery(itemId);
			}
		}

		private void RPC_ApplyDiscovery(long sender, string itemId)
		{
			ReceiveRemoteDiscovery(itemId, live: true);
		}

		private void RPC_RequestSnapshot(long sender)
		{
			if (!((Object)(object)ZNet.instance == (Object)null) && ZNet.instance.IsServer())
			{
				if (!IsValidClientSender(sender))
				{
					Plugin.Log.LogWarning((object)$"Rejected snapshot request from unknown peer {sender}.");
					return;
				}
				Plugin.DebugLog("Snapshot request received from client.");
				SendSnapshot(sender);
			}
		}

		private void RPC_Snapshot(long sender, ZPackage package)
		{
			List<string> list = ReadSnapshot(package);
			Plugin.DebugLog($"Snapshot received with {list.Count} discovery ID(s).");
			foreach (string item in list)
			{
				ReceiveRemoteDiscovery(item, live: false);
			}
		}

		private void ReceiveRemoteDiscovery(string itemId, bool live)
		{
			if (!DiscoveryValidator.IsValidItemId(itemId))
			{
				Plugin.Log.LogWarning((object)("Rejected invalid discovery " + (live ? "broadcast" : "snapshot") + "."));
				return;
			}
			if (live)
			{
				Plugin.DebugLog("Live discovery RPC received: " + itemId);
			}
			_vanillaDiscovery.ApplyOrQueue(itemId);
		}

		private void BroadcastDiscovery(string itemId)
		{
			if ((Object)(object)ZNet.instance == (Object)null || ZRoutedRpc.instance == null)
			{
				return;
			}
			List<ZNetPeer> list = new List<ZNetPeer>();
			foreach (ZNetPeer connectedPeer in ZNet.instance.GetConnectedPeers())
			{
				if (connectedPeer != null && !connectedPeer.m_server && connectedPeer.m_uid != 0L)
				{
					list.Add(connectedPeer);
				}
			}
			Plugin.DebugLog($"Broadcasting discovery '{itemId}' to {list.Count} remote peer(s).");
			foreach (ZNetPeer item in list)
			{
				Plugin.DebugLog($"Sending live discovery '{itemId}' to peer routed ID {item.m_uid}.");
				ZRoutedRpc.instance.InvokeRoutedRPC(item.m_uid, "SharedDiscovery_ApplyDiscovery", new object[1] { itemId });
			}
		}

		private static ZPackage WriteSnapshot(IReadOnlyList<string> itemIds)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: Expected O, but got Unknown
			ZPackage val = new ZPackage();
			val.Write(itemIds.Count);
			foreach (string itemId in itemIds)
			{
				val.Write(itemId);
			}
			return val;
		}

		private static List<string> ReadSnapshot(ZPackage package)
		{
			List<string> list = new List<string>();
			int num;
			try
			{
				num = package.ReadInt();
			}
			catch (Exception ex)
			{
				Plugin.Log.LogWarning((object)("Rejected unreadable shared discovery snapshot: " + ex.Message));
				return list;
			}
			if (num < 0 || num > 4096)
			{
				Plugin.Log.LogWarning((object)$"Rejected shared discovery snapshot with invalid size: {num}");
				return list;
			}
			for (int i = 0; i < num; i++)
			{
				string text;
				try
				{
					text = package.ReadString();
				}
				catch (Exception ex2)
				{
					Plugin.Log.LogWarning((object)("Stopped reading malformed shared discovery snapshot: " + ex2.Message));
					break;
				}
				if (DiscoveryValidator.IsValidItemId(text))
				{
					list.Add(text);
				}
			}
			return list;
		}

		private static bool IsValidClientSender(long sender)
		{
			if (sender == 0L || ZRoutedRpc.instance == null)
			{
				return false;
			}
			if ((Object)(object)ZNet.instance != (Object)null)
			{
				return ZNet.instance.GetPeer(sender) != null;
			}
			return false;
		}

		private static bool VanillaItemExists(string itemId)
		{
			if ((Object)(object)ObjectDB.instance != (Object)null)
			{
				return (Object)(object)ObjectDB.instance.GetItemPrefab(itemId) != (Object)null;
			}
			return false;
		}
	}
}
namespace SharedDiscovery.Discovery
{
	internal sealed class DiscoveryService
	{
		private readonly DiscoveryRegistry _registry = new DiscoveryRegistry();

		private readonly WorldDiscoveryStoreAdapter _store;

		private string? _worldId;

		public IReadOnlyList<string> Snapshot => _registry.Snapshot();

		public bool IsWorldLoaded => _worldId != null;

		public DiscoveryService(WorldDiscoveryStoreAdapter store)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			_store = store;
		}

		public bool EnsureWorldLoaded()
		{
			if (!ModConfig.Enabled.Value || (Object)(object)ZNet.instance == (Object)null || !ZNet.instance.IsServer())
			{
				return false;
			}
			if (!WorldContext.TryGetCurrentWorldId(out string worldId))
			{
				return false;
			}
			if (_worldId == worldId)
			{
				return true;
			}
			Plugin.DebugLog("Loading authoritative SharedDiscovery registry for UID " + worldId + ".");
			_registry.ReplaceWith((IEnumerable<string>)_store.Load(worldId));
			_worldId = worldId;
			Plugin.Log.LogInfo((object)$"Loaded world state '{worldId}': {_registry.Count} discoveries.");
			return true;
		}

		public void Clear()
		{
			_registry.Clear();
			_worldId = null;
		}

		public bool TryAddServerDiscovery(string itemId)
		{
			if (!ModConfig.Enabled.Value || !DiscoveryValidator.IsValidItemId(itemId) || !EnsureWorldLoaded())
			{
				return false;
			}
			if (!_registry.Add(itemId))
			{
				return false;
			}
			Plugin.DebugLog("Server registered global discovery '" + itemId + "'.");
			SaveCurrentWorld();
			Plugin.Log.LogInfo((object)("New global discovery: " + itemId));
			return true;
		}

		public bool Contains(string itemId)
		{
			return _registry.Contains(itemId);
		}

		public void SaveCurrentWorld()
		{
			if (_worldId != null)
			{
				_store.Save(_worldId, _registry.Snapshot());
				Plugin.DebugLog($"Saved world state '{_worldId}': {_registry.Count} discoveries.");
			}
		}
	}
	internal sealed class DiscoverySessionState
	{
		private readonly HashSet<string> _restoredItemIds = new HashSet<string>(StringComparer.Ordinal);

		private Player? _readyPlayer;

		public bool AcceptLocalDiscoveries { get; private set; }

		public bool IsLocalPlayerReady { get; private set; }

		public bool CanApplyRemoteDiscoveries { get; private set; }

		public void CaptureInitialRestoredItems(Player player)
		{
			_restoredItemIds.Clear();
			if ((Object)(object)player == (Object)null || ((Humanoid)player).GetInventory() == null)
			{
				return;
			}
			foreach (ItemData allItem in ((Humanoid)player).GetInventory().GetAllItems())
			{
				string prefabItemId = VanillaItemIdentifier.GetPrefabItemId(allItem);
				if (prefabItemId != null && _restoredItemIds.Add(prefabItemId))
				{
					Plugin.DebugLog("Initial restored item baseline: " + prefabItemId);
				}
			}
		}

		public bool MarkLocalPlayerReady(Player player)
		{
			if ((Object)(object)player == (Object)null || (Object)(object)player != (Object)(object)Player.m_localPlayer)
			{
				return false;
			}
			if (IsLocalPlayerReady && (Object)(object)_readyPlayer == (Object)(object)player)
			{
				return false;
			}
			_readyPlayer = player;
			IsLocalPlayerReady = true;
			CanApplyRemoteDiscoveries = true;
			AcceptLocalDiscoveries = true;
			Plugin.DebugLog("Local player is ready for remote discovery application.");
			return true;
		}

		public bool IsRestoredItem(string itemId)
		{
			return _restoredItemIds.Contains(itemId);
		}

		public void Reset()
		{
			AcceptLocalDiscoveries = false;
			IsLocalPlayerReady = false;
			CanApplyRemoteDiscoveries = false;
			_readyPlayer = null;
			_restoredItemIds.Clear();
			VanillaDiscoveryService.ResetApplyingSharedDiscoveryGuard();
		}
	}
	internal sealed class VanillaDiscoveryService
	{
		private readonly Queue<string> _pending = new Queue<string>();

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

		private readonly Dictionary<string, int> _attempts = new Dictionary<string, int>(StringComparer.Ordinal);

		private bool _processing;

		private int _sessionGeneration;

		public static bool IsApplyingSharedDiscovery { get; private set; }

		public static void ResetApplyingSharedDiscoveryGuard()
		{
			IsApplyingSharedDiscovery = false;
		}

		public void ClearPending()
		{
			_pending.Clear();
			_pendingSet.Clear();
			_attempts.Clear();
			_processing = false;
			_sessionGeneration++;
		}

		public void ApplyOrQueue(string itemId)
		{
			if (ModConfig.Enabled.Value && DiscoveryValidator.IsValidItemId(itemId) && !PlayerAlreadyKnows(itemId))
			{
				if (_pendingSet.Add(itemId))
				{
					_pending.Enqueue(itemId);
					Plugin.DebugLog("Queued remote discovery: " + itemId);
				}
				if (!_processing && (Object)(object)Plugin.Instance != (Object)null)
				{
					((MonoBehaviour)Plugin.Instance).StartCoroutine(ProcessPending(_sessionGeneration));
				}
			}
		}

		public bool PlayerAlreadyKnows(string itemId)
		{
			Player localPlayer = Player.m_localPlayer;
			ItemDrop val = FindItemDrop(itemId);
			if ((Object)(object)localPlayer != (Object)null && (Object)(object)val != (Object)null)
			{
				return localPlayer.IsMaterialKnown(val.m_itemData.m_shared.m_name);
			}
			return false;
		}

		public void ApplySnapshot(IEnumerable<string> itemIds)
		{
			foreach (string itemId in itemIds)
			{
				ApplyOrQueue(itemId);
			}
		}

		private IEnumerator ProcessPending(int generation)
		{
			_processing = true;
			while (generation == _sessionGeneration && _pending.Count > 0)
			{
				string text = _pending.Dequeue();
				_pendingSet.Remove(text);
				Plugin.DebugLog("Applying pending remote discovery: " + text);
				if (!TryApply(text))
				{
					if (!Plugin.Session.CanApplyRemoteDiscoveries || (Object)(object)Player.m_localPlayer == (Object)null || (Object)(object)MessageHud.instance == (Object)null)
					{
						Plugin.DebugLog("Deferred remote discovery '" + text + "': final player not ready.");
					}
					int value;
					int num = ((!_attempts.TryGetValue(text, out value)) ? 1 : (value + 1));
					_attempts[text] = num;
					if (num >= 30)
					{
						Plugin.Log.LogWarning((object)("Shared discovery item prefab was not available after retries: " + text));
						_attempts.Remove(text);
					}
					else
					{
						_pending.Enqueue(text);
						_pendingSet.Add(text);
						yield return (object)new WaitForSeconds(1f);
					}
				}
				else
				{
					_attempts.Remove(text);
					yield return null;
				}
			}
			if (generation == _sessionGeneration)
			{
				_processing = false;
			}
		}

		private static bool TryApply(string itemId)
		{
			Player localPlayer = Player.m_localPlayer;
			if ((Object)(object)localPlayer == (Object)null || (Object)(object)ObjectDB.instance == (Object)null || !Plugin.Session.IsLocalPlayerReady || (Object)(object)MessageHud.instance == (Object)null)
			{
				return false;
			}
			ItemDrop val = FindItemDrop(itemId);
			if ((Object)(object)val == (Object)null)
			{
				return false;
			}
			string name = val.m_itemData.m_shared.m_name;
			if (localPlayer.IsMaterialKnown(name))
			{
				return true;
			}
			try
			{
				IsApplyingSharedDiscovery = true;
				localPlayer.AddKnownItem(val.m_itemData);
				Plugin.DebugLog("Applied remote discovery through Player.AddKnownItem: " + itemId);
			}
			catch (Exception arg)
			{
				Plugin.Log.LogError((object)$"Failed to refresh vanilla knowledge after remote AddKnownItem '{itemId}': {arg}");
				return false;
			}
			finally
			{
				IsApplyingSharedDiscovery = false;
			}
			return true;
		}

		private static ItemDrop FindItemDrop(string itemId)
		{
			if ((Object)(object)ObjectDB.instance == (Object)null)
			{
				return null;
			}
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(itemId);
			if ((Object)(object)itemPrefab == (Object)null)
			{
				return null;
			}
			return itemPrefab.GetComponent<ItemDrop>();
		}
	}
	internal static class VanillaItemIdentifier
	{
		public static string? GetPrefabItemId(ItemData item)
		{
			if (item == null || (Object)(object)ObjectDB.instance == (Object)null)
			{
				return null;
			}
			GameObject val = item.m_dropPrefab;
			if ((Object)(object)val == (Object)null && item.m_shared != null)
			{
				val = ObjectDB.instance.GetItemPrefab(item.m_shared);
			}
			string normalizedPrefabName = GetNormalizedPrefabName(val);
			if (normalizedPrefabName == null)
			{
				return null;
			}
			GameObject itemPrefab = ObjectDB.instance.GetItemPrefab(normalizedPrefabName);
			if (!((Object)(object)itemPrefab != (Object)null) || !((Object)(object)itemPrefab.GetComponent<ItemDrop>() != (Object)null))
			{
				return null;
			}
			return normalizedPrefabName;
		}

		public static string? GetCandidatePrefabName(ItemData item)
		{
			if (item == null)
			{
				return null;
			}
			GameObject val = item.m_dropPrefab;
			if ((Object)(object)val == (Object)null && item.m_shared != null && (Object)(object)ObjectDB.instance != (Object)null)
			{
				val = ObjectDB.instance.GetItemPrefab(item.m_shared);
			}
			return GetNormalizedPrefabName(val);
		}

		private static string? GetNormalizedPrefabName(GameObject? prefab)
		{
			if ((Object)(object)prefab == (Object)null)
			{
				return null;
			}
			string text = StripCloneSuffix(((Object)prefab).name);
			if (!DiscoveryValidator.IsValidItemId(text))
			{
				return null;
			}
			return text;
		}

		private static string StripCloneSuffix(string name)
		{
			if (!name.EndsWith("(Clone)", StringComparison.Ordinal))
			{
				return name;
			}
			return name.Substring(0, name.Length - "(Clone)".Length);
		}
	}
	internal static class WorldContext
	{
		public static bool TryGetCurrentWorldId(out string worldId)
		{
			worldId = string.Empty;
			ZNet instance = ZNet.instance;
			if ((Object)(object)instance == (Object)null)
			{
				return false;
			}
			if (instance.GetWorld() == null)
			{
				return false;
			}
			long worldUID = instance.GetWorldUID();
			if (worldUID == 0L)
			{
				return false;
			}
			string text = worldUID.ToString(CultureInfo.InvariantCulture);
			if (!DiscoveryValidator.IsValidItemId(text))
			{
				return false;
			}
			worldId = text;
			return true;
		}
	}
}
namespace SharedDiscovery.Config
{
	internal static class ModConfig
	{
		public static ConfigEntry<bool> Enabled { get; private set; }

		public static ConfigEntry<bool> DebugLogging { get; private set; }

		public static void Bind(ConfigFile config)
		{
			Enabled = config.Bind<bool>("General", "Enabled", true, "Enable shared item discoveries for the current world.");
			DebugLogging = config.Bind<bool>("General", "DebugLogging", false, "Enable verbose SharedDiscovery debug logs.");
		}
	}
}