Decompiled source of ModSync v1.1.0

plugins/ElfDevelop.ModSync.dll

Decompiled 9 hours ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using ExitGames.Client.Photon;
using Microsoft.CodeAnalysis;
using Photon.Pun;
using Photon.Realtime;
using TMPro;
using UnityEngine;
using Zorro.Core;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: IgnoresAccessChecksTo("Assembly-CSharp")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("0.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
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;
		}
	}
	[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 ModSync
{
	public sealed class ModEntry
	{
		public string Guid { get; }

		public string Name { get; }

		public string Version { get; }

		public ModEntry(string guid, string name, string version)
		{
			Guid = guid;
			Name = name;
			Version = version;
		}

		public override string ToString()
		{
			return Name + " " + Version;
		}
	}
	public sealed class ModListDiff
	{
		public List<ModEntry> MissingOnThem { get; } = new List<ModEntry>();

		public List<ModEntry> OnlyOnThem { get; } = new List<ModEntry>();

		public List<(ModEntry Mine, ModEntry Theirs)> VersionMismatch { get; } = new List<(ModEntry, ModEntry)>();

		public bool IsEmpty
		{
			get
			{
				if (MissingOnThem.Count == 0 && OnlyOnThem.Count == 0)
				{
					return VersionMismatch.Count == 0;
				}
				return false;
			}
		}

		public static ModListDiff Compare(IReadOnlyDictionary<string, ModEntry> mine, IReadOnlyDictionary<string, ModEntry> theirs)
		{
			ModListDiff modListDiff = new ModListDiff();
			foreach (ModEntry item in mine.Values.OrderBy<ModEntry, string>((ModEntry e) => e.Name, StringComparer.OrdinalIgnoreCase))
			{
				if (!theirs.TryGetValue(item.Guid, out ModEntry value))
				{
					modListDiff.MissingOnThem.Add(item);
				}
				else if (value.Version != item.Version)
				{
					modListDiff.VersionMismatch.Add((item, value));
				}
			}
			foreach (ModEntry item2 in theirs.Values.OrderBy<ModEntry, string>((ModEntry e) => e.Name, StringComparer.OrdinalIgnoreCase))
			{
				if (!mine.ContainsKey(item2.Guid))
				{
					modListDiff.OnlyOnThem.Add(item2);
				}
			}
			return modListDiff;
		}

		public static string Serialize(IEnumerable<ModEntry> entries)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (ModEntry entry in entries)
			{
				stringBuilder.Append(Clean(entry.Guid)).Append('\t').Append(Clean(entry.Name))
					.Append('\t')
					.Append(Clean(entry.Version))
					.Append('\n');
			}
			return stringBuilder.ToString();
		}

		public static Dictionary<string, ModEntry> Parse(string data)
		{
			Dictionary<string, ModEntry> dictionary = new Dictionary<string, ModEntry>();
			string[] array = data.Split('\n');
			for (int i = 0; i < array.Length; i++)
			{
				string[] array2 = array[i].Split('\t');
				if (array2.Length == 3 && array2[0].Length != 0)
				{
					dictionary[array2[0]] = new ModEntry(array2[0], array2[1], array2[2]);
				}
			}
			return dictionary;
		}

		private static string Clean(string s)
		{
			return s.Replace('\t', ' ').Replace('\n', ' ').Replace('\r', ' ');
		}
	}
	[ContentWarningPlugin("ElfDevelop.ModSync", "1.1.0", true)]
	[BepInPlugin("ElfDevelop.ModSync", "ModSync", "1.1.0")]
	public class ModSyncPlugin : BaseUnityPlugin
	{
		internal static ConfigEntry<string> IgnoredPlugins;

		internal static ConfigEntry<float> PeerTimeout;

		internal static ConfigEntry<string> Language;

		internal static ManualLogSource Logger { get; private set; }

		internal static bool Russian
		{
			get
			{
				//IL_002f: Unknown result type (might be due to invalid IL or missing references)
				//IL_0036: Invalid comparison between Unknown and I4
				//IL_0038: Unknown result type (might be due to invalid IL or missing references)
				//IL_003f: Invalid comparison between Unknown and I4
				//IL_0041: Unknown result type (might be due to invalid IL or missing references)
				//IL_0047: Invalid comparison between Unknown and I4
				string value = Language.Value;
				if (!(value == "ru"))
				{
					if (value == "en")
					{
						return false;
					}
					return (int)Application.systemLanguage == 30 || (int)Application.systemLanguage == 38 || (int)Application.systemLanguage == 3;
				}
				return true;
			}
		}

		private void Awake()
		{
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_009f: Expected O, but got Unknown
			//IL_00a9: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ae: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b4: Expected O, but got Unknown
			Logger = ((BaseUnityPlugin)this).Logger;
			IgnoredPlugins = ((BaseUnityPlugin)this).Config.Bind<string>("General", "IgnoredPlugins", "com.bepis.bepinex.configurationmanager", "Comma-separated plugin GUIDs that are not compared (client-side only mods).");
			PeerTimeout = ((BaseUnityPlugin)this).Config.Bind<float>("General", "PeerTimeoutSeconds", 15f, "How long to wait for another player's mod list before warning that they don't have ModSync.");
			Language = ((BaseUnityPlugin)this).Config.Bind<string>("General", "Language", "auto", new ConfigDescription("Message language.", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[3] { "auto", "ru", "en" }), Array.Empty<object>()));
			GameObject val = new GameObject("ModSync");
			Object.DontDestroyOnLoad((Object)val);
			val.AddComponent<SyncBehaviour>();
			Logger.LogInfo((object)"ModSync 1.1.0 has loaded!");
		}

		internal static Dictionary<string, ModEntry> BuildLocalList()
		{
			HashSet<string> hashSet = new HashSet<string>(from s in IgnoredPlugins.Value.Split(new char[1] { ',' }, StringSplitOptions.RemoveEmptyEntries)
				select s.Trim(), StringComparer.OrdinalIgnoreCase);
			Dictionary<string, ModEntry> dictionary = new Dictionary<string, ModEntry>();
			foreach (PluginInfo value in Chainloader.PluginInfos.Values)
			{
				string gUID = value.Metadata.GUID;
				if (!hashSet.Contains(gUID))
				{
					dictionary[gUID] = new ModEntry(gUID, value.Metadata.Name, value.Metadata.Version.ToString());
				}
			}
			return dictionary;
		}
	}
	internal class SyncBehaviour : MonoBehaviourPunCallbacks
	{
		private const string PropertyKey = "ElfDevelop.ModSync";

		private const int MaxLines = 8;

		private const float HostSummaryDelay = 3f;

		private readonly Queue<(string Title, string Body)> pending = new Queue<(string, string)>();

		private Dictionary<string, ModEntry> local = new Dictionary<string, ModEntry>();

		private readonly Dictionary<int, string> playerReports = new Dictionary<int, string>();

		private readonly HashSet<int> waitingForPlayer = new HashSet<int>();

		private float summaryAt = -1f;

		private string lastSummary = "";

		private string lastClientReport = "";

		private Coroutine? hostTimeout;

		private bool shrunk;

		private bool savedAutoSizing;

		private float savedMin;

		private float savedMax;

		private float savedSize;

		private static bool Ru => ModSyncPlugin.Russian;

		public override void OnJoinedRoom()
		{
			//IL_0011: 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_0034: Expected O, but got Unknown
			ResetState();
			local = ModSyncPlugin.BuildLocalList();
			Hashtable val = new Hashtable { [(object)"ElfDevelop.ModSync"] = ModListDiff.Serialize(local.Values) };
			PhotonNetwork.LocalPlayer.SetCustomProperties(val, (Hashtable)null, (WebFlags)null);
			ModSyncPlugin.Logger.LogInfo((object)$"Published {local.Count} mods");
			StartRole();
		}

		public override void OnLeftRoom()
		{
			ResetState();
			pending.Clear();
		}

		public override void OnMasterClientSwitched(Player newMasterClient)
		{
			ResetState();
			StartRole();
		}

		public override void OnPlayerEnteredRoom(Player newPlayer)
		{
			if (PhotonNetwork.IsMasterClient)
			{
				WatchPlayer(newPlayer);
			}
		}

		public override void OnPlayerLeftRoom(Player otherPlayer)
		{
			if (PhotonNetwork.IsMasterClient && playerReports.Remove(otherPlayer.ActorNumber))
			{
				ScheduleSummary();
			}
		}

		public override void OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
		{
			if (!targetPlayer.IsLocal && ((Dictionary<object, object>)(object)changedProps).ContainsKey((object)"ElfDevelop.ModSync"))
			{
				if (PhotonNetwork.IsMasterClient)
				{
					HostCompare(targetPlayer);
				}
				else if (targetPlayer.IsMasterClient)
				{
					ClientCompare(targetPlayer);
				}
			}
		}

		private void ResetState()
		{
			((MonoBehaviour)this).StopAllCoroutines();
			hostTimeout = null;
			playerReports.Clear();
			waitingForPlayer.Clear();
			summaryAt = -1f;
			lastSummary = "";
			lastClientReport = "";
		}

		private void StartRole()
		{
			if (local.Count == 0)
			{
				local = ModSyncPlugin.BuildLocalList();
			}
			if (PhotonNetwork.IsMasterClient)
			{
				Player[] playerListOthers = PhotonNetwork.PlayerListOthers;
				foreach (Player player in playerListOthers)
				{
					WatchPlayer(player);
				}
			}
			else if (PhotonNetwork.MasterClient != null)
			{
				Player masterClient = PhotonNetwork.MasterClient;
				if (((Dictionary<object, object>)(object)masterClient.CustomProperties).ContainsKey((object)"ElfDevelop.ModSync"))
				{
					ClientCompare(masterClient);
				}
				else
				{
					hostTimeout = ((MonoBehaviour)this).StartCoroutine(WaitForHost(masterClient));
				}
			}
		}

		private IEnumerator WaitForHost(Player host)
		{
			yield return (object)new WaitForSecondsRealtime(ModSyncPlugin.PeerTimeout.Value);
			if (PhotonNetwork.InRoom && PhotonNetwork.MasterClient == host && !((Dictionary<object, object>)(object)host.CustomProperties).ContainsKey((object)"ElfDevelop.ModSync"))
			{
				ModSyncPlugin.Logger.LogWarning((object)("Host " + host.NickName + " has no ModSync"));
				ShowClient(Ru ? ("У хоста " + host.NickName + " не установлен ModSync, поэтому моды сверить нельзя.") : ("The host " + host.NickName + " doesn't have ModSync, so mods can't be compared."));
			}
		}

		private void ClientCompare(Player host)
		{
			if (hostTimeout != null)
			{
				((MonoBehaviour)this).StopCoroutine(hostTimeout);
				hostTimeout = null;
			}
			if (!(host.CustomProperties[(object)"ElfDevelop.ModSync"] is string data))
			{
				return;
			}
			ModListDiff modListDiff = ModListDiff.Compare(local, ModListDiff.Parse(data));
			if (modListDiff.IsEmpty)
			{
				ModSyncPlugin.Logger.LogInfo((object)("Mods match the host " + host.NickName));
				lastClientReport = "";
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			stringBuilder.Append(Ru ? ("Сравнение с хостом " + host.NickName + ".\n\n") : ("Compared with the host " + host.NickName + ".\n\n"));
			AppendSection(stringBuilder, Ru ? "Нет у тебя:" : "You are missing:", modListDiff.OnlyOnThem.Select((ModEntry e) => e.ToString()));
			AppendSection(stringBuilder, Ru ? "Есть только у тебя:" : "Only you have:", modListDiff.MissingOnThem.Select((ModEntry e) => e.ToString()));
			AppendSection(stringBuilder, Ru ? "Другая версия (у тебя / у хоста):" : "Different version (yours / host's):", modListDiff.VersionMismatch.Select<(ModEntry, ModEntry), string>(((ModEntry Mine, ModEntry Theirs) m) => m.Mine.Name + " " + m.Mine.Version + " / " + m.Theirs.Version));
			string text = stringBuilder.ToString().TrimEnd();
			ModSyncPlugin.Logger.LogWarning((object)text);
			ShowClient(text);
		}

		private void ShowClient(string body)
		{
			if (!(body == lastClientReport))
			{
				lastClientReport = body;
				pending.Enqueue((Ru ? "Моды не совпадают с хостом" : "Your mods differ from the host", body));
			}
		}

		private void WatchPlayer(Player player)
		{
			if (((Dictionary<object, object>)(object)player.CustomProperties).ContainsKey((object)"ElfDevelop.ModSync"))
			{
				HostCompare(player);
			}
			else if (waitingForPlayer.Add(player.ActorNumber))
			{
				((MonoBehaviour)this).StartCoroutine(WaitForPlayer(player));
			}
		}

		private IEnumerator WaitForPlayer(Player player)
		{
			yield return (object)new WaitForSecondsRealtime(ModSyncPlugin.PeerTimeout.Value);
			waitingForPlayer.Remove(player.ActorNumber);
			if (PhotonNetwork.IsMasterClient)
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				if (((currentRoom != null) ? currentRoom.GetPlayer(player.ActorNumber, false) : null) != null && !((Dictionary<object, object>)(object)player.CustomProperties).ContainsKey((object)"ElfDevelop.ModSync"))
				{
					ModSyncPlugin.Logger.LogWarning((object)(player.NickName + " has no ModSync"));
					SetPlayerReport(player, Ru ? "• не установлен ModSync, сверить нельзя\n" : "• doesn't have ModSync, can't compare\n");
				}
			}
		}

		private void HostCompare(Player player)
		{
			if (!(player.CustomProperties[(object)"ElfDevelop.ModSync"] is string data))
			{
				return;
			}
			waitingForPlayer.Remove(player.ActorNumber);
			ModListDiff modListDiff = ModListDiff.Compare(local, ModListDiff.Parse(data));
			if (modListDiff.IsEmpty)
			{
				ModSyncPlugin.Logger.LogInfo((object)(player.NickName + ": mods match"));
				SetPlayerReport(player, "");
				return;
			}
			StringBuilder stringBuilder = new StringBuilder();
			AppendLine(stringBuilder, Ru ? "нет" : "missing", modListDiff.MissingOnThem.Select((ModEntry e) => e.ToString()));
			AppendLine(stringBuilder, Ru ? "лишние" : "extra", modListDiff.OnlyOnThem.Select((ModEntry e) => e.ToString()));
			AppendLine(stringBuilder, Ru ? "другая версия (у тебя / у него)" : "different version (yours / theirs)", modListDiff.VersionMismatch.Select<(ModEntry, ModEntry), string>(((ModEntry Mine, ModEntry Theirs) m) => m.Mine.Name + " " + m.Mine.Version + " / " + m.Theirs.Version));
			ModSyncPlugin.Logger.LogWarning((object)$"{player.NickName}: mods differ\n{stringBuilder}");
			SetPlayerReport(player, stringBuilder.ToString());
		}

		private void SetPlayerReport(Player player, string report)
		{
			playerReports[player.ActorNumber] = report;
			ScheduleSummary();
		}

		private void ScheduleSummary()
		{
			summaryAt = Time.realtimeSinceStartup + 3f;
		}

		private void ShowHostSummary()
		{
			summaryAt = -1f;
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<int, string> item in from p in playerReports
				where p.Value.Length > 0
				orderby p.Key
				select p)
			{
				Room currentRoom = PhotonNetwork.CurrentRoom;
				object obj;
				if (currentRoom == null)
				{
					obj = null;
				}
				else
				{
					Player player = currentRoom.GetPlayer(item.Key, false);
					obj = ((player != null) ? player.NickName : null);
				}
				if (obj == null)
				{
					obj = "?";
				}
				string value = (string)obj;
				stringBuilder.Append("<b>").Append(value).Append("</b>\n")
					.Append(item.Value)
					.Append('\n');
			}
			string text = stringBuilder.ToString().TrimEnd();
			if (text.Length == 0 || text == lastSummary)
			{
				lastSummary = text;
				return;
			}
			lastSummary = text;
			pending.Enqueue((Ru ? "У игроков другие моды" : "Players have different mods", text));
		}

		private static void AppendSection(StringBuilder sb, string header, IEnumerable<string> items)
		{
			List<string> list = items.ToList();
			if (list.Count == 0)
			{
				return;
			}
			sb.Append("<b>").Append(header).Append("</b>\n");
			if (list.Count <= 8)
			{
				foreach (string item in list)
				{
					sb.Append("• ").Append(item).Append('\n');
				}
			}
			else
			{
				sb.Append(string.Join(", ", list)).Append('\n');
			}
			sb.Append('\n');
		}

		private static void AppendLine(StringBuilder sb, string label, IEnumerable<string> items)
		{
			List<string> list = items.ToList();
			if (list.Count > 0)
			{
				sb.Append("• ").Append(label).Append(": ")
					.Append(string.Join(", ", list))
					.Append('\n');
			}
		}

		private void Update()
		{
			if (summaryAt > 0f && Time.realtimeSinceStartup >= summaryAt)
			{
				ShowHostSummary();
			}
			Modal instance = RetrievableResourceSingleton<Modal>.Instance;
			if (!((Object)(object)instance == (Object)null))
			{
				if (!instance.Open)
				{
					RestoreBodyText(instance);
				}
				if (pending.Count != 0 && !instance.Open)
				{
					var (text, text2) = pending.Dequeue();
					Modal.ShowError(text, text2);
					ShrinkBodyText(instance);
				}
			}
		}

		private void ShrinkBodyText(Modal modal)
		{
			TextMeshProUGUI bodyText = modal.BodyText;
			if (!((Object)(object)bodyText == (Object)null) && !shrunk)
			{
				savedAutoSizing = ((TMP_Text)bodyText).enableAutoSizing;
				savedMin = ((TMP_Text)bodyText).fontSizeMin;
				savedMax = ((TMP_Text)bodyText).fontSizeMax;
				savedSize = ((TMP_Text)bodyText).fontSize;
				((TMP_Text)bodyText).fontSizeMax = savedSize;
				((TMP_Text)bodyText).fontSizeMin = Mathf.Min(10f, savedSize);
				((TMP_Text)bodyText).enableAutoSizing = true;
				shrunk = true;
			}
		}

		private void RestoreBodyText(Modal modal)
		{
			if (shrunk && !((Object)(object)modal.BodyText == (Object)null))
			{
				TextMeshProUGUI bodyText = modal.BodyText;
				((TMP_Text)bodyText).enableAutoSizing = savedAutoSizing;
				((TMP_Text)bodyText).fontSizeMin = savedMin;
				((TMP_Text)bodyText).fontSizeMax = savedMax;
				((TMP_Text)bodyText).fontSize = savedSize;
				shrunk = false;
			}
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		public IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}