using System;
using System.Collections;
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 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 HarmonyLib;
using Microsoft.CodeAnalysis;
using ModSync.Networking;
using Photon.Pun;
using Photon.Realtime;
using UnityEngine;
[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETFramework,Version=v4.7.1", FrameworkDisplayName = ".NET Framework 4.7.1")]
[assembly: AssemblyCompany("ModSync")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+fc6bc7fe0b23e6eb94f7536cdd35c7e2d3cd2055")]
[assembly: AssemblyProduct("ModSync")]
[assembly: AssemblyTitle("ModSync")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.Embedded]
internal sealed class EmbeddedAttribute : Attribute
{
}
}
namespace System.Runtime.CompilerServices
{
[CompilerGenerated]
[Microsoft.CodeAnalysis.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]
[Microsoft.CodeAnalysis.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]
[Microsoft.CodeAnalysis.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
{
internal static class ConfigSync
{
private static readonly ManualLogSource Log = Plugin.Logger;
internal static Dictionary<string, string> GetLocalConfigs()
{
Dictionary<string, string> dictionary = new Dictionary<string, string>();
string configPath = Paths.ConfigPath;
foreach (KeyValuePair<string, PluginInfo> pluginInfo in Chainloader.PluginInfos)
{
if (pluginInfo.Key == "com.Jaz.modsync")
{
continue;
}
string path = Path.Combine(configPath, pluginInfo.Key + ".cfg");
if (File.Exists(path))
{
try
{
dictionary[Path.GetFileName(path)] = File.ReadAllText(path);
}
catch (Exception ex)
{
Log.LogWarning((object)("[ConfigSync] Could not read config for " + pluginInfo.Key + ": " + ex.Message));
}
}
}
return dictionary;
}
internal static byte[] Serialize(Dictionary<string, string> configs)
{
using MemoryStream memoryStream = new MemoryStream();
using BinaryWriter binaryWriter = new BinaryWriter(memoryStream, Encoding.UTF8);
binaryWriter.Write(configs.Count);
foreach (KeyValuePair<string, string> config in configs)
{
binaryWriter.Write(config.Key);
binaryWriter.Write(config.Value);
}
return memoryStream.ToArray();
}
internal static Dictionary<string, string> Deserialize(byte[] data)
{
Dictionary<string, string> dictionary = new Dictionary<string, string>();
using MemoryStream input = new MemoryStream(data);
using BinaryReader binaryReader = new BinaryReader(input, Encoding.UTF8);
int num = binaryReader.ReadInt32();
for (int i = 0; i < num; i++)
{
string key = binaryReader.ReadString();
string value = binaryReader.ReadString();
dictionary[key] = value;
}
return dictionary;
}
internal static int ApplyInMemory(Dictionary<string, string> receivedConfigs)
{
int num = 0;
foreach (KeyValuePair<string, string> receivedConfig in receivedConfigs)
{
string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(receivedConfig.Key);
if (!Chainloader.PluginInfos.TryGetValue(fileNameWithoutExtension, out var value))
{
Log.LogDebug((object)("[ConfigSync] Skipping config for uninstalled mod: " + fileNameWithoutExtension));
continue;
}
ConfigFile config = value.Instance.Config;
Dictionary<string, Dictionary<string, string>> dictionary = ParseIni(receivedConfig.Value);
bool saveOnConfigSet = config.SaveOnConfigSet;
config.SaveOnConfigSet = false;
try
{
foreach (ConfigDefinition key in config.Keys)
{
if (dictionary.TryGetValue(key.Section, out var value2) && value2.TryGetValue(key.Key, out var value3))
{
ConfigEntryBase val = config[key];
try
{
val.BoxedValue = TomlTypeConverter.ConvertToValue(value3, val.SettingType);
num++;
}
catch (Exception ex)
{
Log.LogWarning((object)("[ConfigSync] Could not apply " + fileNameWithoutExtension + "/" + key.Section + "/" + key.Key + ": " + ex.Message));
}
}
}
}
finally
{
config.SaveOnConfigSet = saveOnConfigSet;
}
Log.LogInfo((object)("[ConfigSync] Applied in-memory config for: " + fileNameWithoutExtension));
}
return num;
}
private static Dictionary<string, Dictionary<string, string>> ParseIni(string content)
{
Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>(StringComparer.OrdinalIgnoreCase);
Dictionary<string, string> value = null;
string[] array = content.Split(new char[1] { '\n' });
for (int i = 0; i < array.Length; i++)
{
string text = array[i].Trim();
if (string.IsNullOrEmpty(text) || text.StartsWith("#"))
{
continue;
}
if (text.StartsWith("[") && text.EndsWith("]"))
{
string key = text.Substring(1, text.Length - 2).Trim();
if (!dictionary.TryGetValue(key, out value))
{
value = (dictionary[key] = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase));
}
continue;
}
int num = text.IndexOf('=');
if (num > 0 && value != null)
{
string key2 = text.Substring(0, num).Trim();
string value2 = text.Substring(num + 1).Trim();
value[key2] = value2;
}
}
return dictionary;
}
}
internal static class ModChecker
{
internal static string[] GetLocalModList()
{
return (from kvp in Chainloader.PluginInfos
where kvp.Key != "com.Jaz.modsync"
select $"{kvp.Key}|{kvp.Value.Metadata.Version}" into s
orderby s
select s).ToArray();
}
internal static MismatchReport Compare(string[] localMods, string[] remoteMods)
{
HashSet<string> hashSet = new HashSet<string>(localMods);
HashSet<string> hashSet2 = new HashSet<string>(remoteMods);
string[] missingFromLocal = hashSet2.Except(hashSet).ToArray();
string[] missingFromRemote = hashSet.Except(hashSet2).ToArray();
return new MismatchReport(missingFromLocal, missingFromRemote);
}
}
internal class MismatchReport
{
internal string[] MissingFromLocal { get; }
internal string[] MissingFromRemote { get; }
internal bool HasMismatches
{
get
{
if (MissingFromLocal.Length == 0)
{
return MissingFromRemote.Length != 0;
}
return true;
}
}
internal MismatchReport(string[] missingFromLocal, string[] missingFromRemote)
{
MissingFromLocal = missingFromLocal;
MissingFromRemote = missingFromRemote;
}
internal string FormatForDisplay()
{
List<string> list = new List<string>();
if (MissingFromLocal.Length != 0)
{
list.Add("[ModSync] Mods you're missing:");
string[] missingFromLocal = MissingFromLocal;
foreach (string entry in missingFromLocal)
{
list.Add(" - " + FormatEntry(entry));
}
}
if (MissingFromRemote.Length != 0)
{
list.Add("[ModSync] Mods the host doesn't have:");
string[] missingFromLocal = MissingFromRemote;
foreach (string entry2 in missingFromLocal)
{
list.Add(" + " + FormatEntry(entry2));
}
}
return string.Join("\n", list);
}
private static string FormatEntry(string entry)
{
string[] array = entry.Split(new char[1] { '|' });
if (array.Length != 2)
{
return entry;
}
return array[0] + " (v" + array[1] + ")";
}
}
internal static class MyPluginInfo
{
internal const string PLUGIN_GUID = "com.Jaz.modsync";
internal const string PLUGIN_NAME = "ModSync";
internal const string PLUGIN_VERSION = "1.0.0";
}
[BepInPlugin("com.Jaz.modsync", "ModSync", "1.0.0")]
public class Plugin : BaseUnityPlugin
{
private readonly Harmony _harmony = new Harmony("com.Jaz.modsync");
internal static Plugin Instance { get; private set; }
internal static ManualLogSource Logger { get; private set; }
internal static ConfigEntry<bool> EnableModCheck { get; private set; }
internal static ConfigEntry<bool> EnableConfigSync { get; private set; }
internal static ConfigEntry<bool> NotifyOnMismatch { get; private set; }
internal static ConfigEntry<bool> BlockJoinOnMismatch { get; private set; }
private void Awake()
{
Instance = this;
Logger = ((BaseUnityPlugin)this).Logger;
EnableModCheck = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableModCheck", true, "Check if players joining your room have the same mods installed.");
EnableConfigSync = ((BaseUnityPlugin)this).Config.Bind<bool>("General", "EnableConfigSync", true, "Sync host's mod configs to clients when they join the room.");
NotifyOnMismatch = ((BaseUnityPlugin)this).Config.Bind<bool>("ModCheck", "NotifyOnMismatch", true, "Show a notification when a mod mismatch is detected.");
BlockJoinOnMismatch = ((BaseUnityPlugin)this).Config.Bind<bool>("ModCheck", "BlockJoinOnMismatch", false, "Kick clients that have different mods than the host. (Host only)");
_harmony.PatchAll();
((Component)this).gameObject.AddComponent<ModSyncNetwork>();
Logger.LogInfo((object)"ModSync v1.0.0 loaded.");
}
private void OnDestroy()
{
_harmony.UnpatchSelf();
}
}
}
namespace ModSync.Networking
{
public class ModSyncNetwork : MonoBehaviour, IInRoomCallbacks, IMatchmakingCallbacks, IOnEventCallback
{
[CompilerGenerated]
private sealed class <ShowNotification>d__23 : IEnumerator<object>, IDisposable, IEnumerator
{
private int <>1__state;
private object <>2__current;
object IEnumerator<object>.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
object IEnumerator.Current
{
[DebuggerHidden]
get
{
return <>2__current;
}
}
[DebuggerHidden]
public <ShowNotification>d__23(int <>1__state)
{
this.<>1__state = <>1__state;
}
[DebuggerHidden]
void IDisposable.Dispose()
{
<>1__state = -2;
}
private bool MoveNext()
{
switch (<>1__state)
{
default:
return false;
case 0:
<>1__state = -1;
<>2__current = null;
<>1__state = 1;
return true;
case 1:
<>1__state = -1;
return false;
}
}
bool IEnumerator.MoveNext()
{
//ILSpy generated this explicit interface implementation from .override directive in MoveNext
return this.MoveNext();
}
[DebuggerHidden]
void IEnumerator.Reset()
{
throw new NotSupportedException();
}
}
private const byte EVT_REQUEST_MOD_SYNC = 145;
private const byte EVT_MOD_LIST_PAYLOAD = 146;
private const byte EVT_CONFIG_PAYLOAD = 147;
private void OnEnable()
{
PhotonNetwork.AddCallbackTarget((object)this);
}
private void OnDisable()
{
PhotonNetwork.RemoveCallbackTarget((object)this);
}
void IInRoomCallbacks.OnPlayerEnteredRoom(Player newPlayer)
{
if (PhotonNetwork.IsMasterClient)
{
Plugin.Logger.LogInfo((object)("[ModSync] " + newPlayer.NickName + " joined — pushing mod list."));
PushModListTo(newPlayer);
if (Plugin.EnableConfigSync.Value)
{
PushConfigsTo(newPlayer);
}
}
}
void IInRoomCallbacks.OnPlayerLeftRoom(Player otherPlayer)
{
}
void IInRoomCallbacks.OnRoomPropertiesUpdate(Hashtable propertiesThatChanged)
{
}
void IInRoomCallbacks.OnPlayerPropertiesUpdate(Player targetPlayer, Hashtable changedProps)
{
}
void IInRoomCallbacks.OnMasterClientSwitched(Player newMasterClient)
{
}
void IMatchmakingCallbacks.OnJoinedRoom()
{
//IL_0017: Unknown result type (might be due to invalid IL or missing references)
//IL_001d: Expected O, but got Unknown
//IL_003f: Unknown result type (might be due to invalid IL or missing references)
if (!PhotonNetwork.IsMasterClient)
{
Plugin.Logger.LogInfo((object)"[ModSync] Joined room — requesting mod sync from host.");
RaiseEventOptions val = new RaiseEventOptions();
val.TargetActors = new int[1] { PhotonNetwork.MasterClient.ActorNumber };
RaiseEventOptions val2 = val;
PhotonNetwork.RaiseEvent((byte)145, (object)null, val2, SendOptions.SendReliable);
}
}
void IMatchmakingCallbacks.OnFriendListUpdate(List<FriendInfo> friendList)
{
}
void IMatchmakingCallbacks.OnCreatedRoom()
{
}
void IMatchmakingCallbacks.OnCreateRoomFailed(short returnCode, string message)
{
}
void IMatchmakingCallbacks.OnJoinRoomFailed(short returnCode, string message)
{
}
void IMatchmakingCallbacks.OnJoinRandomFailed(short returnCode, string message)
{
}
void IMatchmakingCallbacks.OnLeftRoom()
{
}
private static void PushModListTo(Player target)
{
//IL_0013: Unknown result type (might be due to invalid IL or missing references)
//IL_0019: Expected O, but got Unknown
//IL_0037: Unknown result type (might be due to invalid IL or missing references)
if (Plugin.EnableModCheck.Value)
{
string[] localModList = ModChecker.GetLocalModList();
RaiseEventOptions val = new RaiseEventOptions();
val.TargetActors = new int[1] { target.ActorNumber };
RaiseEventOptions val2 = val;
PhotonNetwork.RaiseEvent((byte)146, (object)localModList, val2, SendOptions.SendReliable);
}
}
private static void PushConfigsTo(Player target)
{
//IL_0016: Unknown result type (might be due to invalid IL or missing references)
//IL_001c: Expected O, but got Unknown
//IL_003a: Unknown result type (might be due to invalid IL or missing references)
Dictionary<string, string> localConfigs = ConfigSync.GetLocalConfigs();
if (localConfigs.Count != 0)
{
byte[] array = ConfigSync.Serialize(localConfigs);
RaiseEventOptions val = new RaiseEventOptions();
val.TargetActors = new int[1] { target.ActorNumber };
RaiseEventOptions val2 = val;
PhotonNetwork.RaiseEvent((byte)147, (object)array, val2, SendOptions.SendReliable);
}
}
void IOnEventCallback.OnEvent(EventData photonEvent)
{
switch (photonEvent.Code)
{
case 145:
HandleModSyncRequest(photonEvent);
break;
case 146:
HandleModListPayload(photonEvent);
break;
case 147:
HandleConfigPayload(photonEvent);
break;
}
}
private void HandleModSyncRequest(EventData evt)
{
if (!PhotonNetwork.IsMasterClient)
{
return;
}
Room currentRoom = PhotonNetwork.CurrentRoom;
if (currentRoom != null && currentRoom.Players.TryGetValue(evt.Sender, out var value))
{
Plugin.Logger.LogInfo((object)("[ModSync] " + value.NickName + " requested mod sync."));
PushModListTo(value);
if (Plugin.EnableConfigSync.Value)
{
PushConfigsTo(value);
}
}
}
private void HandleModListPayload(EventData evt)
{
if (!Plugin.EnableModCheck.Value)
{
return;
}
if (!(evt.CustomData is string[] remoteMods))
{
Plugin.Logger.LogWarning((object)"[ModSync] Received malformed mod list payload.");
return;
}
MismatchReport mismatchReport = ModChecker.Compare(ModChecker.GetLocalModList(), remoteMods);
if (!mismatchReport.HasMismatches)
{
Plugin.Logger.LogInfo((object)"[ModSync] Mod list matches host.");
return;
}
string text = mismatchReport.FormatForDisplay();
Plugin.Logger.LogWarning((object)text);
if (Plugin.NotifyOnMismatch.Value)
{
((MonoBehaviour)this).StartCoroutine(ShowNotification(text));
}
if (Plugin.BlockJoinOnMismatch.Value && PhotonNetwork.IsMasterClient && evt.Sender != PhotonNetwork.LocalPlayer.ActorNumber)
{
Room currentRoom = PhotonNetwork.CurrentRoom;
if (currentRoom != null && currentRoom.Players.TryGetValue(evt.Sender, out var value))
{
Plugin.Logger.LogWarning((object)("[ModSync] Kicking " + value.NickName + " due to mod mismatch."));
PhotonNetwork.CloseConnection(value);
}
}
}
private void HandleConfigPayload(EventData evt)
{
if (Plugin.EnableConfigSync.Value)
{
if (!(evt.CustomData is byte[] data))
{
Plugin.Logger.LogWarning((object)"[ModSync] Received malformed config payload.");
return;
}
Plugin.Logger.LogInfo((object)"[ModSync] Received config bundle from host — applying in-memory...");
int num = ConfigSync.ApplyInMemory(ConfigSync.Deserialize(data));
Plugin.Logger.LogInfo((object)$"[ModSync] Applied {num} setting(s) from host config.");
}
}
[IteratorStateMachine(typeof(<ShowNotification>d__23))]
private IEnumerator ShowNotification(string message)
{
//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
return new <ShowNotification>d__23(0);
}
}
}