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)
{
}
}
}