Decompiled source of BoplModSyncer v1.0.0

BoplModSyncer.dll

Decompiled 4 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.Serialization;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Cryptography;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using BepInEx;
using BepInEx.Bootstrap;
using BepInEx.Configuration;
using BepInEx.Logging;
using BoplModSyncer.Utils;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Steamworks;
using Steamworks.Data;
using TMPro;
using TinyJson;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityEngine.UI;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: TargetFramework(".NETFramework,Version=v4.6", FrameworkDisplayName = ".NET Framework 4.6")]
[assembly: AssemblyCompany("BoplModSyncer")]
[assembly: AssemblyConfiguration("Thunderstore")]
[assembly: AssemblyDescription("Checks if all connected players have necessary mods")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+6b35826ca40c07445c3c7a81ed56537f4e736479")]
[assembly: AssemblyProduct("BoplModSyncer")]
[assembly: AssemblyTitle("BoplModSyncer")]
[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]
	internal sealed class IsReadOnlyAttribute : Attribute
	{
	}
	[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 TinyJson
{
	public static class JSONParser
	{
		[ThreadStatic]
		private static Stack<List<string>> splitArrayPool;

		[ThreadStatic]
		private static StringBuilder stringBuilder;

		[ThreadStatic]
		private static Dictionary<Type, Dictionary<string, FieldInfo>> fieldInfoCache;

		[ThreadStatic]
		private static Dictionary<Type, Dictionary<string, PropertyInfo>> propertyInfoCache;

		public static T FromJson<T>(this string json)
		{
			if (propertyInfoCache == null)
			{
				propertyInfoCache = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
			}
			if (fieldInfoCache == null)
			{
				fieldInfoCache = new Dictionary<Type, Dictionary<string, FieldInfo>>();
			}
			if (stringBuilder == null)
			{
				stringBuilder = new StringBuilder();
			}
			if (splitArrayPool == null)
			{
				splitArrayPool = new Stack<List<string>>();
			}
			stringBuilder.Length = 0;
			for (int i = 0; i < json.Length; i++)
			{
				char c = json[i];
				if (c == '"')
				{
					i = AppendUntilStringEnd(appendEscapeCharacter: true, i, json);
				}
				else if (!char.IsWhiteSpace(c))
				{
					stringBuilder.Append(c);
				}
			}
			return (T)ParseValue(typeof(T), stringBuilder.ToString());
		}

		private static int AppendUntilStringEnd(bool appendEscapeCharacter, int startIdx, string json)
		{
			stringBuilder.Append(json[startIdx]);
			for (int i = startIdx + 1; i < json.Length; i++)
			{
				if (json[i] == '\\')
				{
					if (appendEscapeCharacter)
					{
						stringBuilder.Append(json[i]);
					}
					stringBuilder.Append(json[i + 1]);
					i++;
				}
				else
				{
					if (json[i] == '"')
					{
						stringBuilder.Append(json[i]);
						return i;
					}
					stringBuilder.Append(json[i]);
				}
			}
			return json.Length - 1;
		}

		private static List<string> Split(string json)
		{
			List<string> list = ((splitArrayPool.Count > 0) ? splitArrayPool.Pop() : new List<string>());
			list.Clear();
			if (json.Length == 2)
			{
				return list;
			}
			int num = 0;
			stringBuilder.Length = 0;
			for (int i = 1; i < json.Length - 1; i++)
			{
				switch (json[i])
				{
				case '[':
				case '{':
					num++;
					break;
				case ']':
				case '}':
					num--;
					break;
				case '"':
					i = AppendUntilStringEnd(appendEscapeCharacter: true, i, json);
					continue;
				case ',':
				case ':':
					if (num == 0)
					{
						list.Add(stringBuilder.ToString());
						stringBuilder.Length = 0;
						continue;
					}
					break;
				}
				stringBuilder.Append(json[i]);
			}
			list.Add(stringBuilder.ToString());
			return list;
		}

		internal static object ParseValue(Type type, string json)
		{
			if (type == typeof(string))
			{
				if (json.Length <= 2)
				{
					return string.Empty;
				}
				StringBuilder stringBuilder = new StringBuilder(json.Length);
				for (int i = 1; i < json.Length - 1; i++)
				{
					if (json[i] == '\\' && i + 1 < json.Length - 1)
					{
						int num = "\"\\nrtbf/".IndexOf(json[i + 1]);
						if (num >= 0)
						{
							stringBuilder.Append("\"\\\n\r\t\b\f/"[num]);
							i++;
							continue;
						}
						if (json[i + 1] == 'u' && i + 5 < json.Length - 1 && uint.TryParse(json.Substring(i + 2, 4), NumberStyles.AllowHexSpecifier, null, out var result))
						{
							stringBuilder.Append((char)result);
							i += 5;
							continue;
						}
					}
					stringBuilder.Append(json[i]);
				}
				return stringBuilder.ToString();
			}
			if (type.IsPrimitive)
			{
				return Convert.ChangeType(json, type, CultureInfo.InvariantCulture);
			}
			if (type == typeof(decimal))
			{
				decimal.TryParse(json, NumberStyles.Float, CultureInfo.InvariantCulture, out var result2);
				return result2;
			}
			if (type == typeof(DateTime))
			{
				DateTime.TryParse(json.Replace("\"", ""), CultureInfo.InvariantCulture, DateTimeStyles.None, out var result3);
				return result3;
			}
			if (json == "null")
			{
				return null;
			}
			if (type.IsEnum)
			{
				if (json[0] == '"')
				{
					json = json.Substring(1, json.Length - 2);
				}
				try
				{
					return Enum.Parse(type, json, ignoreCase: false);
				}
				catch
				{
					return 0;
				}
			}
			if (type.IsArray)
			{
				Type elementType = type.GetElementType();
				if (json[0] != '[' || json[json.Length - 1] != ']')
				{
					return null;
				}
				List<string> list = Split(json);
				Array array = Array.CreateInstance(elementType, list.Count);
				for (int j = 0; j < list.Count; j++)
				{
					array.SetValue(ParseValue(elementType, list[j]), j);
				}
				splitArrayPool.Push(list);
				return array;
			}
			if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(List<>))
			{
				Type type2 = type.GetGenericArguments()[0];
				if (json[0] != '[' || json[json.Length - 1] != ']')
				{
					return null;
				}
				List<string> list2 = Split(json);
				IList list3 = (IList)type.GetConstructor(new Type[1] { typeof(int) }).Invoke(new object[1] { list2.Count });
				for (int k = 0; k < list2.Count; k++)
				{
					list3.Add(ParseValue(type2, list2[k]));
				}
				splitArrayPool.Push(list2);
				return list3;
			}
			if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Dictionary<, >))
			{
				Type[] genericArguments = type.GetGenericArguments();
				Type type3 = genericArguments[0];
				Type type4 = genericArguments[1];
				if (type3 != typeof(string))
				{
					return null;
				}
				if (json[0] != '{' || json[json.Length - 1] != '}')
				{
					return null;
				}
				List<string> list4 = Split(json);
				if (list4.Count % 2 != 0)
				{
					return null;
				}
				IDictionary dictionary = (IDictionary)type.GetConstructor(new Type[1] { typeof(int) }).Invoke(new object[1] { list4.Count / 2 });
				for (int l = 0; l < list4.Count; l += 2)
				{
					if (list4[l].Length > 2)
					{
						string key = list4[l].Substring(1, list4[l].Length - 2);
						object value = ParseValue(type4, list4[l + 1]);
						dictionary[key] = value;
					}
				}
				return dictionary;
			}
			if (type == typeof(object))
			{
				return ParseAnonymousValue(json);
			}
			if (json[0] == '{' && json[json.Length - 1] == '}')
			{
				return ParseObject(type, json);
			}
			return null;
		}

		private static object ParseAnonymousValue(string json)
		{
			if (json.Length == 0)
			{
				return null;
			}
			if (json[0] == '{' && json[json.Length - 1] == '}')
			{
				List<string> list = Split(json);
				if (list.Count % 2 != 0)
				{
					return null;
				}
				Dictionary<string, object> dictionary = new Dictionary<string, object>(list.Count / 2);
				for (int i = 0; i < list.Count; i += 2)
				{
					dictionary[list[i].Substring(1, list[i].Length - 2)] = ParseAnonymousValue(list[i + 1]);
				}
				return dictionary;
			}
			if (json[0] == '[' && json[json.Length - 1] == ']')
			{
				List<string> list2 = Split(json);
				List<object> list3 = new List<object>(list2.Count);
				for (int j = 0; j < list2.Count; j++)
				{
					list3.Add(ParseAnonymousValue(list2[j]));
				}
				return list3;
			}
			if (json[0] == '"' && json[json.Length - 1] == '"')
			{
				string text = json.Substring(1, json.Length - 2);
				return text.Replace("\\", string.Empty);
			}
			if (char.IsDigit(json[0]) || json[0] == '-')
			{
				if (json.Contains("."))
				{
					double.TryParse(json, NumberStyles.Float, CultureInfo.InvariantCulture, out var result);
					return result;
				}
				int.TryParse(json, out var result2);
				return result2;
			}
			if (json == "true")
			{
				return true;
			}
			if (json == "false")
			{
				return false;
			}
			return null;
		}

		private static Dictionary<string, T> CreateMemberNameDictionary<T>(T[] members) where T : MemberInfo
		{
			Dictionary<string, T> dictionary = new Dictionary<string, T>(StringComparer.OrdinalIgnoreCase);
			foreach (T val in members)
			{
				if (val.IsDefined(typeof(IgnoreDataMemberAttribute), inherit: true))
				{
					continue;
				}
				string name = val.Name;
				if (val.IsDefined(typeof(DataMemberAttribute), inherit: true))
				{
					DataMemberAttribute dataMemberAttribute = (DataMemberAttribute)Attribute.GetCustomAttribute(val, typeof(DataMemberAttribute), inherit: true);
					if (!string.IsNullOrEmpty(dataMemberAttribute.Name))
					{
						name = dataMemberAttribute.Name;
					}
				}
				dictionary.Add(name, val);
			}
			return dictionary;
		}

		private static object ParseObject(Type type, string json)
		{
			object uninitializedObject = FormatterServices.GetUninitializedObject(type);
			List<string> list = Split(json);
			if (list.Count % 2 != 0)
			{
				return uninitializedObject;
			}
			if (!fieldInfoCache.TryGetValue(type, out var value))
			{
				value = CreateMemberNameDictionary(type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
				fieldInfoCache.Add(type, value);
			}
			if (!propertyInfoCache.TryGetValue(type, out var value2))
			{
				value2 = CreateMemberNameDictionary(type.GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy));
				propertyInfoCache.Add(type, value2);
			}
			for (int i = 0; i < list.Count; i += 2)
			{
				if (list[i].Length > 2)
				{
					string key = list[i].Substring(1, list[i].Length - 2);
					string json2 = list[i + 1];
					PropertyInfo value4;
					if (value.TryGetValue(key, out var value3))
					{
						value3.SetValue(uninitializedObject, ParseValue(value3.FieldType, json2));
					}
					else if (value2.TryGetValue(key, out value4))
					{
						value4.SetValue(uninitializedObject, ParseValue(value4.PropertyType, json2), null);
					}
				}
			}
			return uninitializedObject;
		}
	}
	public static class JSONWriter
	{
		public static string ToJson(this object item)
		{
			StringBuilder stringBuilder = new StringBuilder();
			AppendValue(stringBuilder, item);
			return stringBuilder.ToString();
		}

		private static void AppendValue(StringBuilder stringBuilder, object item)
		{
			if (item == null)
			{
				stringBuilder.Append("null");
				return;
			}
			Type type = item.GetType();
			if (type == typeof(string) || type == typeof(char))
			{
				stringBuilder.Append('"');
				string text = item.ToString();
				for (int i = 0; i < text.Length; i++)
				{
					if (text[i] < ' ' || text[i] == '"' || text[i] == '\\')
					{
						stringBuilder.Append('\\');
						int num = "\"\\\n\r\t\b\f".IndexOf(text[i]);
						if (num >= 0)
						{
							stringBuilder.Append("\"\\nrtbf"[num]);
						}
						else
						{
							stringBuilder.AppendFormat("u{0:X4}", (uint)text[i]);
						}
					}
					else
					{
						stringBuilder.Append(text[i]);
					}
				}
				stringBuilder.Append('"');
			}
			else if (type == typeof(byte) || type == typeof(sbyte))
			{
				stringBuilder.Append(item.ToString());
			}
			else if (type == typeof(short) || type == typeof(ushort))
			{
				stringBuilder.Append(item.ToString());
			}
			else if (type == typeof(int) || type == typeof(uint))
			{
				stringBuilder.Append(item.ToString());
			}
			else if (type == typeof(long) || type == typeof(ulong))
			{
				stringBuilder.Append(item.ToString());
			}
			else if (type == typeof(float))
			{
				stringBuilder.Append(((float)item).ToString(CultureInfo.InvariantCulture));
			}
			else if (type == typeof(double))
			{
				stringBuilder.Append(((double)item).ToString(CultureInfo.InvariantCulture));
			}
			else if (type == typeof(decimal))
			{
				stringBuilder.Append(((decimal)item).ToString(CultureInfo.InvariantCulture));
			}
			else if (type == typeof(bool))
			{
				stringBuilder.Append(((bool)item) ? "true" : "false");
			}
			else if (type == typeof(DateTime))
			{
				stringBuilder.Append('"');
				stringBuilder.Append(((DateTime)item).ToString(CultureInfo.InvariantCulture));
				stringBuilder.Append('"');
			}
			else if (type.IsEnum)
			{
				stringBuilder.Append('"');
				stringBuilder.Append(item.ToString());
				stringBuilder.Append('"');
			}
			else if (item is IList)
			{
				stringBuilder.Append('[');
				bool flag = true;
				IList list = item as IList;
				for (int j = 0; j < list.Count; j++)
				{
					if (flag)
					{
						flag = false;
					}
					else
					{
						stringBuilder.Append(',');
					}
					AppendValue(stringBuilder, list[j]);
				}
				stringBuilder.Append(']');
			}
			else
			{
				if (!type.IsGenericType || !(type.GetGenericTypeDefinition() == typeof(Dictionary<, >)))
				{
					return;
				}
				Type type2 = type.GetGenericArguments()[0];
				if (type2 != typeof(string))
				{
					stringBuilder.Append("{}");
					return;
				}
				stringBuilder.Append('{');
				IDictionary dictionary = item as IDictionary;
				bool flag2 = true;
				foreach (object key in dictionary.Keys)
				{
					if (flag2)
					{
						flag2 = false;
					}
					else
					{
						stringBuilder.Append(',');
					}
					stringBuilder.Append('"');
					stringBuilder.Append((string)key);
					stringBuilder.Append("\":");
					AppendValue(stringBuilder, dictionary[key]);
				}
				stringBuilder.Append('}');
			}
		}
	}
}
namespace BoplModSyncer
{
	internal static class Patches
	{
		private static readonly string checksumField = GameUtils.GenerateField("checksum");

		private static readonly string hostModListField = GameUtils.GenerateField("modlist");

		private static readonly string hostConfigListField = GameUtils.GenerateField("configlist");

		private static readonly string memberHasSyncerField = GameUtils.GenerateField("syncer");

		private static bool firstJoin = true;

		private static bool hostSetupDone = false;

		private static bool isHost = false;

		public static void OnEnterLobby_Prefix(Lobby lobby)
		{
			//IL_00df: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f4: Unknown result type (might be due to invalid IL or missing references)
			//IL_0093: 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)
			hostSetupDone = (isHost = false);
			if (SteamManager.LocalPlayerIsLobbyOwner)
			{
				if (firstJoin && Plugin.lastLobbyId.Value != 0)
				{
					Plugin.logger.LogMessage((object)("rejoining: " + Plugin.lastLobbyId.Value));
					new Traverse((object)SteamManager.instance).Method("TryJoinLobby", new object[1] { Plugin.lastLobbyId.Value }).GetValue();
				}
				else
				{
					OnHostJoin(lobby);
				}
				return;
			}
			((Lobby)(ref lobby)).SetMemberData(memberHasSyncerField, "1");
			string data = ((Lobby)(ref lobby)).GetData(checksumField);
			firstJoin = false;
			Plugin.lastLobbyId.Value = 0uL;
			if (data == Plugin.CHECKSUM)
			{
				SyncConfigs(lobby);
			}
			else if (string.IsNullOrEmpty(data))
			{
				HostDoesntHaveSyncer(lobby);
			}
			else
			{
				ModMismatch(lobby);
			}
		}

		private static void LeaveLobby(string message = null)
		{
			SteamManager.instance.LeaveLobby();
			Plugin.logger.LogWarning((object)("Left lobby because: '" + message + "'"));
		}

		private static void HostDoesntHaveSyncer(Lobby lobby)
		{
			LeaveLobby("host doesnt have syncer");
			PanelMaker.InstantiatePanel(Plugin.noSyncerPanel);
		}

		private static void InstallMods(OnlineModData[] toInstallMods, LocalModData[] toDeleteMods)
		{
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			Queue<OnlineModData> modsToDownload = new Queue<OnlineModData>();
			for (int i = 0; i < toInstallMods.Length; i++)
			{
				OnlineModData item = toInstallMods[i];
				if (item.Link != "" && item.DoInstall)
				{
					modsToDownload.Enqueue(item);
				}
			}
			GameObject installingPanel = PanelMaker.InstantiatePanel(Plugin.installingPanel);
			Slider progressBar = PanelMaker.GetProgressBarComp();
			TextMeshProUGUI percentageText = ((Component)installingPanel.transform.Find("ProgressBar/Percentage")).GetComponent<TextMeshProUGUI>();
			TextMeshProUGUI infoText = PanelMaker.GetInfoText();
			TextMeshProUGUI textArea = PanelMaker.GetTextAreaText();
			Button okButton = PanelMaker.GetOkButtonComp();
			((Selectable)okButton).interactable = false;
			((Graphic)textArea).color = Color.red;
			Directory.CreateDirectory(GameUtils.DownloadedModsPath);
			downloadNext();
			void downloadComplete()
			{
				//IL_0042: Unknown result type (might be due to invalid IL or missing references)
				//IL_004c: Expected O, but got Unknown
				((TMP_Text)infoText).text = "Press OK to restart game";
				((TMP_Text)PanelMaker.GetTitleText()).text = "Downloading completed!";
				((Selectable)okButton).interactable = true;
				((UnityEvent)okButton.onClick).AddListener((UnityAction)delegate
				{
					GameUtils.RestartGameAfterDownload(toDeleteMods);
				});
			}
			void downloadNext()
			{
				if (!modsToDownload.Any())
				{
					downloadComplete();
				}
				else
				{
					OnlineModData downloadingMod = modsToDownload.Dequeue();
					WebClient client = new WebClient();
					client.OpenReadCompleted += async delegate(object sender, OpenReadCompletedEventArgs args)
					{
						try
						{
							if (args.Cancelled)
							{
								throw new Exception("cancelled");
							}
							if (args.Error != null)
							{
								throw args.Error;
							}
							string[] linkParts = downloadingMod.Link.Split(new char[1] { '/' });
							string name = string.Join("-", linkParts[5], linkParts[6], linkParts[7]) + ".zip";
							string path = Path.Combine(GameUtils.DownloadedModsPath, name);
							Plugin.logger.LogInfo((object)("downloading: " + name));
							((TMP_Text)infoText).text = downloadingMod.Guid + " v" + downloadingMod.Version;
							using FileStream file = File.Create(path);
							await BaseUtils.CopyToAsync(args.Result, file, new SynchronousProgress<int>(delegate(int value)
							{
								progressBar.value = value;
								((TMP_Text)percentageText).text = $"{value}%";
							}));
							downloadNext();
						}
						catch (Exception ex2)
						{
							Exception ex = ex2;
							Plugin.logger.LogError((object)$"error while downloading: {ex}");
							((TMP_Text)textArea).text = ex.Message;
							Button continueButton = ((Component)installingPanel.transform.Find("Continue Button")).GetComponent<Button>();
							((Component)continueButton).gameObject.SetActive(true);
							((UnityEvent)continueButton.onClick).AddListener((UnityAction)delegate
							{
								((Component)continueButton).gameObject.SetActive(false);
								((TMP_Text)textArea).text = "";
								downloadNext();
							});
						}
						finally
						{
							client.Dispose();
						}
					};
					client.OpenReadAsync(new Uri(downloadingMod.Link));
				}
			}
		}

		private static void ModMismatch(Lobby lobby)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_017c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0183: Expected O, but got Unknown
			//IL_0375: Unknown result type (might be due to invalid IL or missing references)
			//IL_037f: Expected O, but got Unknown
			string hostModListText = ((Lobby)(ref lobby)).GetData(hostModListField);
			string data = ((Lobby)(ref lobby)).GetData(hostConfigListField);
			Plugin.lastLobbyId.Value = SteamId.op_Implicit(((Lobby)(ref lobby)).Id);
			LeaveLobby("Missing mods");
			MethodInfo methodInfo = null;
			MethodInfo[] methods = typeof(ConfigFile).GetMethods();
			foreach (MethodInfo methodInfo2 in methods)
			{
				if (!(methodInfo2.Name != "Bind"))
				{
					ParameterInfo[] parameters = methodInfo2.GetParameters();
					if (parameters[0].ParameterType == typeof(ConfigDefinition))
					{
						methodInfo = methodInfo2;
						break;
					}
				}
			}
			if (methodInfo == null)
			{
				throw new NullReferenceException("No bind method was found");
			}
			string[] array = hostModListText.Split(new char[1] { '|' }, StringSplitOptions.RemoveEmptyEntries);
			OnlineModData[] toInstallMods = new OnlineModData[array.Length];
			int num = 0;
			Dictionary<string, HostConfigEntry[]> hostConfigs = GameUtils.GetHostConfigs(data);
			string[] array2 = array;
			foreach (string text in array2)
			{
				string[] array3 = text.Split(new char[1] { ',' });
				OnlineModData onlineModData = new OnlineModData(array3[0], array3[1], array3[2], array3[3]);
				if (hostConfigs.TryGetValue(onlineModData.Guid, out var value))
				{
					ConfigFile val = new ConfigFile(Path.Combine(Paths.ConfigPath, onlineModData.Guid + ".cfg"), true);
					HostConfigEntry[] array4 = value;
					for (int k = 0; k < array4.Length; k++)
					{
						HostConfigEntry hostConfigEntry = array4[k];
						object obj = ((!(hostConfigEntry.Type == typeof(string))) ? ((!hostConfigEntry.Type.IsValueType) ? null : Activator.CreateInstance(hostConfigEntry.Type)) : "");
						object obj2 = methodInfo.MakeGenericMethod(hostConfigEntry.Type).Invoke(val, new object[3] { hostConfigEntry.Definition, obj, null });
						((ConfigEntryBase)((obj2 is ConfigEntryBase) ? obj2 : null)).SetSerializedValue(hostConfigEntry.Value);
					}
					val.Save();
				}
				if (!Plugin.mods.TryGetValue(onlineModData.Guid, out var value2) || !(value2.Hash == onlineModData.Hash))
				{
					toInstallMods[num] = onlineModData;
					num++;
				}
			}
			Array.Resize(ref toInstallMods, num);
			LocalModData[] toDeleteMods = Plugin.mods.Values.Where((LocalModData e) => !hostModListText.Contains(e.Hash)).ToArray();
			Plugin.logger.LogWarning((object)("missing:\n\t- " + string.Join("\n\t- ", toInstallMods.Select((OnlineModData m) => m.Guid + " v" + m.Version))));
			Plugin.logger.LogWarning((object)("to delete:\n\t- " + string.Join("\n\t- ", toDeleteMods.Select((LocalModData m) => $"{m.Plugin.Metadata.GUID} {m.Plugin.Metadata.Version}"))));
			GameObject val2 = PanelMaker.InstantiatePanel(Plugin.missingModsPanel, (UnityAction)delegate
			{
				InstallMods(toInstallMods, toDeleteMods);
			});
			Transform row = val2.transform.Find("NeededModList/Viewport/Content/tmprow");
			LinkClicker linkClicker = val2.AddComponent<LinkClicker>();
			for (int l = 0; l < toInstallMods.Length; l++)
			{
				makeInstallRow(l);
			}
			for (int n = 0; n < toDeleteMods.Length; n++)
			{
				makeDeleteRow(n);
			}
			void makeDeleteRow(int index)
			{
				makeRow(index, toDelete: true);
			}
			void makeInstallRow(int index)
			{
				makeRow(index, toDelete: false);
			}
			void makeRow(int index, bool toDelete)
			{
				//IL_017d: Unknown result type (might be due to invalid IL or missing references)
				//IL_0184: Unknown result type (might be due to invalid IL or missing references)
				IModData modData2;
				if (!toDelete)
				{
					IModData modData = toInstallMods[index];
					modData2 = modData;
				}
				else
				{
					IModData modData = toDeleteMods[index];
					modData2 = modData;
				}
				IModData modData3 = modData2;
				Transform val3 = Object.Instantiate<Transform>(row, row.parent);
				((Component)val3).gameObject.SetActive(true);
				TextMeshProUGUI component = ((Component)val3.Find("Background/Label")).GetComponent<TextMeshProUGUI>();
				Toggle component2 = ((Component)val3).GetComponent<Toggle>();
				StringBuilder stringBuilder = new StringBuilder(modData3.Guid).Append(" v").Append(modData3.Version).Append(" (")
					.Append(modData3.Hash.Substring(0, 4))
					.Append(")");
				if (!string.IsNullOrEmpty(modData3.Link))
				{
					string[] array5 = modData3.Link.Split(new char[1] { '/' });
					string value3 = "https://thunderstore.io/c/bopl-battle/p/" + array5[5] + "/" + array5[6] + "/";
					stringBuilder.Append(" <link=").Append(value3).Append("><color=blue><b>link</b></color></link>");
				}
				((TMP_Text)component).text = stringBuilder.ToString();
				linkClicker.textMeshes.Add(component);
				if (toDelete)
				{
					((TMP_Text)component).fontStyle = (FontStyles)(((TMP_Text)component).fontStyle | 0x40);
					((UnityEvent<bool>)(object)component2.onValueChanged).AddListener((UnityAction<bool>)delegate(bool isOn)
					{
						toDeleteMods[index].DoDelete = isOn;
					});
					component2.isOn = true;
				}
				else if (!string.IsNullOrEmpty(modData3.Link))
				{
					((UnityEvent<bool>)(object)component2.onValueChanged).AddListener((UnityAction<bool>)delegate(bool isOn)
					{
						toInstallMods[index].DoInstall = isOn;
					});
					component2.isOn = true;
				}
				else
				{
					((Selectable)component2).interactable = false;
				}
			}
		}

		private static void SyncConfigs(Lobby lobby)
		{
			//IL_01bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c9: Expected O, but got Unknown
			//IL_011c: Unknown result type (might be due to invalid IL or missing references)
			Dictionary<string, HostConfigEntry[]> hostConfigs = GameUtils.GetHostConfigs(((Lobby)(ref lobby)).GetData(hostConfigListField));
			Dictionary<string, List<KeyValuePair<ConfigEntryBase, string>>> newConfigs = new Dictionary<string, List<KeyValuePair<ConfigEntryBase, string>>>();
			bool flag = true;
			foreach (KeyValuePair<string, LocalModData> mod in Plugin.mods)
			{
				ConfigFile config = mod.Value.Plugin.Instance.Config;
				List<KeyValuePair<ConfigEntryBase, string>> list = new List<KeyValuePair<ConfigEntryBase, string>>();
				if (!hostConfigs.TryGetValue(mod.Key, out var value))
				{
					continue;
				}
				foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in config)
				{
					ConfigEntryBase value2 = item.Value;
					string text = "";
					HostConfigEntry[] array = value;
					for (int i = 0; i < array.Length; i++)
					{
						HostConfigEntry hostConfigEntry = array[i];
						if (hostConfigEntry.Definition.Equals(value2.Definition))
						{
							text = hostConfigEntry.Value;
							break;
						}
					}
					if (flag && text != value2.GetSerializedValue())
					{
						flag = false;
						Plugin.lastLobbyId.Value = SteamId.op_Implicit(((Lobby)(ref lobby)).Id);
						LeaveLobby("syncing configs");
					}
					if (!flag)
					{
						list.Add(new KeyValuePair<ConfigEntryBase, string>(value2, text));
					}
				}
				if (!flag)
				{
					newConfigs.Add(mod.Key, list);
				}
			}
			if (flag)
			{
				return;
			}
			GameObject val = PanelMaker.InstantiatePanel(Plugin.restartPanel, (UnityAction)delegate
			{
				Directory.CreateDirectory(GameUtils.OldConfigsPath);
				foreach (KeyValuePair<string, LocalModData> mod2 in Plugin.mods)
				{
					ConfigFile config2 = mod2.Value.Plugin.Instance.Config;
					string destFileName = Path.Combine(GameUtils.OldConfigsPath, Path.GetFileName(config2.ConfigFilePath));
					try
					{
						File.Copy(config2.ConfigFilePath, destFileName);
					}
					catch (IOException)
					{
					}
					config2.SaveOnConfigSet = false;
					foreach (KeyValuePair<ConfigEntryBase, string> item2 in newConfigs[mod2.Key])
					{
						item2.Key.SetSerializedValue(item2.Value);
					}
					config2.Save();
				}
				GameUtils.RestartGameAfterSync();
			});
		}

		private static void OnHostJoin(Lobby lobby)
		{
			isHost = true;
			((Lobby)(ref lobby)).SetData(checksumField, Plugin.CHECKSUM);
			Dictionary<string, List<string[]>> dictionary = new Dictionary<string, List<string[]>>();
			StringBuilder stringBuilder = new StringBuilder();
			foreach (KeyValuePair<string, LocalModData> mod in Plugin.mods)
			{
				LocalModData value = mod.Value;
				ConfigFile config = value.Plugin.Instance.Config;
				stringBuilder.Append(mod.Key).Append(',').Append(value.Version)
					.Append(',')
					.Append(value.Link)
					.Append(',')
					.Append(value.Hash)
					.Append('|');
				if (config.Count == 0)
				{
					continue;
				}
				List<string[]> list = new List<string[]>();
				foreach (KeyValuePair<ConfigDefinition, ConfigEntryBase> item in config)
				{
					ConfigEntryBase value2 = item.Value;
					string text = value2.SettingType.FullName;
					if (Type.GetType(text) == null)
					{
						text = value2.SettingType.AssemblyQualifiedName;
					}
					list.Add(new string[3]
					{
						item.Key.MyToString(),
						text,
						value2.GetSerializedValue()
					});
				}
				dictionary.Add(mod.Key, list);
			}
			if (stringBuilder.Length > 0)
			{
				stringBuilder.RemoveLast();
			}
			((Lobby)(ref lobby)).SetData(hostModListField, stringBuilder.ToString());
			((Lobby)(ref lobby)).SetData(hostConfigListField, dictionary.ToJson());
			hostSetupDone = true;
		}

		internal static void OnLobbyMemberJoinedCallback_Postfix(Lobby lobby, Friend friend)
		{
			//IL_0007: Unknown result type (might be due to invalid IL or missing references)
			//IL_0008: Unknown result type (might be due to invalid IL or missing references)
			//IL_000e: Unknown result type (might be due to invalid IL or missing references)
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			if (!isHost)
			{
				return;
			}
			if (!hostSetupDone)
			{
				int num = SteamManager.instance.connectedPlayers.FindIndex((SteamConnection e) => SteamId.op_Implicit(e.id) == SteamId.op_Implicit(friend.Id));
				if (num != -1)
				{
					SteamManager.instance.KickPlayer(num);
					Plugin.logger.LogWarning((object)("Kicked \"" + ((Friend)(ref friend)).Name + "\" because host(you) still havent finished booting!"));
				}
			}
			else
			{
				((MonoBehaviour)Plugin.plugin).StartCoroutine(waitForField());
			}
			IEnumerator waitForField()
			{
				yield return (object)new WaitForSeconds(1f);
				if (!(((Lobby)(ref lobby)).GetMemberData(friend, memberHasSyncerField) == "1"))
				{
					int playerIndex = SteamManager.instance.connectedPlayers.FindIndex((SteamConnection e) => SteamId.op_Implicit(e.id) == SteamId.op_Implicit(friend.Id));
					if (playerIndex != -1)
					{
						SteamManager.instance.KickPlayer(playerIndex);
						Plugin.logger.LogWarning((object)("Kicked \"" + ((Friend)(ref friend)).Name + "\" because he doesnt has syncer!"));
					}
				}
			}
		}

		internal static void JoinLobby_Prefix()
		{
			if ((Object)(object)PanelMaker.currentPanel != (Object)null)
			{
				((UnityEvent)PanelMaker.GetCancelButtonComp().onClick).Invoke();
			}
		}
	}
	[BepInPlugin("com.almafa64.BoplModSyncer", "BoplModSyncer", "1.0.0")]
	[BepInProcess("BoplBattle.exe")]
	public class Plugin : BaseUnityPlugin
	{
		internal const string THUNDERSTORE_BOPL_MODS = "https://thunderstore.io/c/bopl-battle/api/v1/package";

		internal const string GITHUB_CLIENT_ONLY_GUIDS = "https://raw.githubusercontent.com/almafa64/almafa64-bopl-mods/dev/BoplModSyncer/client_only_guids.txt";

		private static readonly Dictionary<string, LocalModData> _mods = new Dictionary<string, LocalModData>();

		public static readonly ReadOnlyDictionary<string, LocalModData> mods = new ReadOnlyDictionary<string, LocalModData>(_mods);

		public static readonly bool IsDemo = Path.GetFileName(Paths.GameRootPath) == "Bopl Battle Demo";

		internal static Harmony harmony;

		internal static ManualLogSource logger;

		internal static ConfigFile config;

		internal static Plugin plugin;

		internal static ConfigEntry<ulong> lastLobbyId;

		internal static string _checksum;

		internal static readonly HashSet<string> _clientOnlyGuids = new HashSet<string>();

		internal static GameObject genericPanel;

		internal static GameObject missingModsPanel;

		internal static GameObject noSyncerPanel;

		internal static GameObject installingPanel;

		internal static GameObject restartPanel;

		private static GameObject checksumTextObj;

		public static string CHECKSUM => _checksum ?? throw new Exception("CHECKSUM hasn't been calculated");

		private void Awake()
		{
			//IL_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_001b: Expected O, but got Unknown
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e9: Expected O, but got Unknown
			//IL_0116: Unknown result type (might be due to invalid IL or missing references)
			//IL_0123: Expected O, but got Unknown
			//IL_0157: Unknown result type (might be due to invalid IL or missing references)
			//IL_0165: Expected O, but got Unknown
			harmony = new Harmony(((BaseUnityPlugin)this).Info.Metadata.GUID);
			logger = ((BaseUnityPlugin)this).Logger;
			config = ((BaseUnityPlugin)this).Config;
			plugin = this;
			WebClient webClient = new WebClient();
			string[] array = webClient.DownloadString("https://raw.githubusercontent.com/almafa64/almafa64-bopl-mods/dev/BoplModSyncer/client_only_guids.txt").Split(new char[1] { '\n' });
			foreach (string text in array)
			{
				_clientOnlyGuids.Add(text.Trim());
			}
			lastLobbyId = config.Bind<ulong>("BoplModSyncer", "last lobby id", 0uL, (ConfigDescription)null);
			SceneManager.sceneLoaded += OnSceneLoaded;
			harmony.Patch((MethodBase)AccessTools.Method(typeof(SteamManager), "OnLobbyEnteredCallback", (Type[])null, (Type[])null), new HarmonyMethod(typeof(Patches), "OnEnterLobby_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method(typeof(SteamManager), "OnLobbyMemberJoinedCallback", (Type[])null, (Type[])null), (HarmonyMethod)null, new HarmonyMethod(typeof(Patches), "OnLobbyMemberJoinedCallback_Postfix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			harmony.Patch((MethodBase)AccessTools.Method("Steamworks.ISteamMatchmaking:JoinLobby", new Type[1] { typeof(SteamId) }, (Type[])null), new HarmonyMethod(typeof(Patches), "JoinLobby_Prefix", (Type[])null), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
			AssetBundle val = AssetBundle.LoadFromStream(BaseUtils.GetResourceStream("BoplModSyncer", "PanelBundle"));
			genericPanel = val.LoadAsset<GameObject>("GenericPanel");
			val.Unload(false);
			GameUtils.Init();
			Directory.CreateDirectory(GameUtils.MyCachePath);
		}

		private void Start()
		{
			WebClient webClient = new WebClient();
			List<object> list = (List<object>)webClient.DownloadString("https://thunderstore.io/c/bopl-battle/api/v1/package").FromJson<object>();
			Dictionary<string, Dictionary<string, string>> dictionary = new Dictionary<string, Dictionary<string, string>>();
			foreach (object item in list)
			{
				Dictionary<string, object> dictionary2 = item as Dictionary<string, object>;
				Dictionary<string, string> dictionary3 = new Dictionary<string, string>();
				foreach (object item2 in (List<object>)dictionary2["versions"])
				{
					Dictionary<string, object> dictionary4 = (Dictionary<string, object>)item2;
					dictionary3.Add((string)dictionary4["version_number"], (string)dictionary4["download_url"]);
				}
				dictionary.Add((string)dictionary2["full_name"], dictionary3);
			}
			List<string> list2 = new List<string>();
			foreach (PluginInfo value2 in Chainloader.PluginInfos.Values)
			{
				if (!_clientOnlyGuids.Contains(value2.Metadata.GUID))
				{
					string text = BaseUtils.ChecksumFile(value2.Location);
					logger.LogInfo((object)(value2.Metadata.GUID + " - " + text));
					list2.Add(text);
					Manifest manifest = GameUtils.GetManifest(value2);
					string fileName = Path.GetFileName(manifest?.Directory);
					string text2 = fileName?.Substring(0, fileName.LastIndexOf('-'));
					string link = ((text2 == null) ? "" : GeneralExtensions.GetValueSafe<string, string>(GeneralExtensions.GetValueSafe<string, Dictionary<string, string>>(dictionary, text2), manifest.Version));
					LocalModData localModData = new LocalModData(link);
					localModData.Manifest = manifest;
					localModData.Plugin = value2;
					localModData.Hash = text;
					LocalModData value = localModData;
					_mods.Add(value2.Metadata.GUID, value);
				}
			}
			MakeChecksumText(BaseUtils.CombineHashes(list2));
			PanelMaker.MakeGenericPanel(ref genericPanel);
			noSyncerPanel = PanelMaker.MakeNoSyncerPanel(genericPanel);
			missingModsPanel = PanelMaker.MakeMissingModsPanel(genericPanel);
			installingPanel = PanelMaker.MakeInstallingPanel(genericPanel);
			restartPanel = PanelMaker.MakeRestartPanel(genericPanel);
			Object.Destroy((Object)(object)genericPanel);
			genericPanel = null;
		}

		private void MakeChecksumText(string checksum)
		{
			//IL_0064: Unknown result type (might be due to invalid IL or missing references)
			if (_checksum != null)
			{
				throw new Exception("Checksum text was already made!");
			}
			_checksum = checksum;
			Transform canvas = PanelUtils.GetCanvas();
			GameObject val = GameObject.Find("ExitText");
			GameObject val2 = Object.Instantiate<GameObject>(val, canvas);
			((Object)val2).name = "hashText";
			Object.Destroy((Object)(object)val2.GetComponent<LocalizedText>());
			TextMeshProUGUI component = val2.GetComponent<TextMeshProUGUI>();
			((TMP_Text)component).transform.position = val.transform.position;
			((TMP_Text)component).text = checksum;
			checksumTextObj = Object.Instantiate<GameObject>(val2);
			((Object)checksumTextObj).hideFlags = (HideFlags)1;
			Object.DontDestroyOnLoad((Object)(object)checksumTextObj);
			Object.Destroy((Object)(object)val2);
			ShowChecksumText(10);
		}

		private void ShowChecksumText(int ypos)
		{
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0032: Unknown result type (might be due to invalid IL or missing references)
			//IL_0037: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			if (!((Object)(object)checksumTextObj == (Object)null))
			{
				GameObject val = Object.Instantiate<GameObject>(checksumTextObj, PanelUtils.GetCanvas());
				Vector3 val2 = Camera.main.WorldToScreenPoint(val.transform.position);
				val2.y = ypos;
				val.transform.position = Camera.main.ScreenToWorldPoint(val2);
			}
		}

		private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
		{
			if (((Scene)(ref scene)).name == "MainMenu")
			{
				OnMainMenuloaded();
			}
			else if (((Scene)(ref scene)).name.Contains("Select"))
			{
				OnSelectMenuLoaded();
			}
		}

		private void OnMainMenuloaded()
		{
			ShowChecksumText(10);
		}

		private void OnSelectMenuLoaded()
		{
			ShowChecksumText(1500);
		}
	}
	public interface IModData
	{
		string Guid { get; }

		string Version { get; }

		string Link { get; }

		string Hash { get; }
	}
	public struct OnlineModData : IModData
	{
		public string Guid { get; internal set; }

		public string Version { get; internal set; }

		public string Link { get; internal set; }

		public string Hash { get; internal set; }

		public bool DoInstall { get; internal set; }

		public OnlineModData(string guid, string version, string link, string hash)
		{
			Guid = guid;
			Version = version;
			Link = link;
			Hash = hash;
			DoInstall = false;
		}

		public override readonly string ToString()
		{
			return "version: '" + Version + "', link: '" + Link + "', guid: '" + Guid + "', hash: '" + Hash + "'";
		}
	}
	public struct LocalModData : IModData
	{
		private PluginInfo _plugin;

		private Manifest _manifest;

		public string Guid { get; private set; }

		public string Version { get; private set; }

		public string Hash { get; internal set; }

		public string Link { get; internal set; }

		public bool DoDelete { get; internal set; }

		public PluginInfo Plugin
		{
			readonly get
			{
				return _plugin;
			}
			internal set
			{
				if (_plugin != null)
				{
					throw new Exception("Plugin was already set!");
				}
				_plugin = value;
				PluginInfo plugin = _plugin;
				Guid = ((plugin != null) ? plugin.Metadata.GUID : null);
				if (Version == null)
				{
					PluginInfo plugin2 = _plugin;
					string text2 = (Version = ((plugin2 != null) ? plugin2.Metadata.Version.ToString() : null));
				}
			}
		}

		public Manifest Manifest
		{
			readonly get
			{
				return _manifest;
			}
			internal set
			{
				if (_manifest != null)
				{
					throw new Exception("Manifest was already set!");
				}
				_manifest = value;
				if (_manifest != null)
				{
					Version = _manifest.Version;
				}
			}
		}

		public LocalModData(string link)
		{
			Guid = null;
			Version = null;
			Hash = null;
			_plugin = null;
			_manifest = null;
			Link = link;
			DoDelete = false;
		}

		public override readonly string ToString()
		{
			return "name: '" + Plugin.Metadata.Name + "', version: '" + Version + "', link: '" + Link + "', guid: '" + Guid + "', hash: '" + Hash + "'";
		}
	}
	public struct HostConfigEntry
	{
		internal Type Type { get; private set; }

		internal string Value { get; private set; }

		internal ConfigDefinition Definition { get; private set; }

		public HostConfigEntry(ConfigDefinition definition, Type type, string value)
		{
			Type = type;
			Value = value;
			Definition = definition;
		}
	}
	public class Manifest
	{
		public string Name { get; private set; }

		public string Version { get; private set; }

		public string Website { get; private set; }

		public string Description { get; private set; }

		public ReadOnlyCollection<string> Dependencies { get; private set; }

		public string Directory { get; private set; }

		internal Manifest(ManifestJSON json, string dir)
		{
			Name = json.name;
			Version = json.version_number;
			Website = json.website_url;
			Description = json.description;
			Dependencies = new ReadOnlyCollection<string>(json.dependencies);
			Directory = dir;
		}
	}
	internal class ManifestJSON
	{
		public string name = null;

		public string version_number = null;

		public string website_url = null;

		public string description = null;

		public string[] dependencies = null;
	}
	public static class TypeExnteders
	{
		public static StringBuilder RemoveLast(this StringBuilder sb)
		{
			return sb.Remove(sb.Length - 1, 1);
		}

		public static string MyToString(this ConfigDefinition def)
		{
			return def.Section + "=" + def.Key;
		}
	}
	internal class LinkClicker : MonoBehaviour, IPointerClickHandler, IEventSystemHandler
	{
		public List<TextMeshProUGUI> textMeshes = new List<TextMeshProUGUI>();

		public void OnPointerClick(PointerEventData eventData)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0048: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			foreach (TextMeshProUGUI textMesh in textMeshes)
			{
				int num = TMP_TextUtilities.FindIntersectingLink((TMP_Text)(object)textMesh, Vector2.op_Implicit(eventData.position), Camera.current);
				if (num == -1)
				{
					continue;
				}
				TMP_LinkInfo val = ((TMP_Text)textMesh).textInfo.linkInfo[num];
				Application.OpenURL(((TMP_LinkInfo)(ref val)).GetLinkID());
				break;
			}
		}
	}
	public static class PluginInfo
	{
		public const string PLUGIN_GUID = "BoplModSyncer";

		public const string PLUGIN_NAME = "BoplModSyncer";

		public const string PLUGIN_VERSION = "1.0.0";
	}
}
namespace BoplModSyncer.Utils
{
	public class BaseUtils
	{
		public static string BytesToString(byte[] bytes)
		{
			return BitConverter.ToString(bytes).Replace("-", "");
		}

		public static string ChecksumFile(string path)
		{
			using SHA256 sHA = SHA256.Create();
			using FileStream fileStream = File.OpenRead(path);
			byte[] bytes = sHA.ComputeHash(fileStream);
			return BytesToString(bytes) + fileStream.Length;
		}

		public static string CombineHashes(List<string> hashes)
		{
			StringBuilder stringBuilder = new StringBuilder();
			foreach (string hash in hashes)
			{
				stringBuilder.Append(hash);
			}
			using SHA256 sHA = SHA256.Create();
			return BytesToString(sHA.ComputeHash(Encoding.UTF8.GetBytes(stringBuilder.ToString())));
		}

		public static async Task CopyToAsync(Stream source, Stream destination, IProgress<int> progress, int bufferSize = 4096, CancellationToken cancellationToken = default(CancellationToken))
		{
			byte[] buffer = new byte[bufferSize];
			long totalBytesRead = 0L;
			while (true)
			{
				int num;
				int bytesRead = (num = await source.ReadAsync(buffer, 0, buffer.Length, cancellationToken));
				if (num <= 0)
				{
					break;
				}
				await destination.WriteAsync(buffer, 0, bytesRead, cancellationToken);
				cancellationToken.ThrowIfCancellationRequested();
				totalBytesRead += bytesRead;
				progress.Report(Convert.ToInt32(totalBytesRead * 100 / source.Length));
			}
		}

		public static Stream GetResourceStream(string namespaceName, string path)
		{
			return Assembly.GetExecutingAssembly().GetManifestResourceStream(namespaceName + "." + path);
		}

		public static string GetRelativePath(string path, string relativeDirectory)
		{
			return path.Substring(path.IndexOf(relativeDirectory) + relativeDirectory.Length);
		}
	}
	public sealed class SynchronousProgress<T> : IProgress<T>
	{
		private readonly Action<T> _callback;

		public SynchronousProgress(Action<T> callback)
		{
			_callback = callback;
			base..ctor();
		}

		void IProgress<T>.Report(T data)
		{
			_callback(data);
		}
	}
	internal static class GameUtils
	{
		private const string DataPrefix = "almafa64>";

		public static string MyCachePath { get; private set; }

		public static string DownloadedModsPath { get; private set; }

		public static string OldConfigsPath { get; private set; }

		public static string OldPluginPath { get; private set; }

		internal static void Init()
		{
			MyCachePath = Path.Combine(Paths.CachePath, ((BaseUnityPlugin)Plugin.plugin).Info.Metadata.GUID);
			DownloadedModsPath = Path.Combine(MyCachePath, "downloaded");
			OldConfigsPath = Path.Combine(MyCachePath, "configs_old");
			OldPluginPath = Path.Combine(MyCachePath, "plugins_old");
		}

		public static void CancelSyncing(WebClient client = null)
		{
			Plugin.lastLobbyId.Value = 0uL;
			client?.CancelAsync();
			try
			{
				Directory.Delete(DownloadedModsPath, recursive: true);
			}
			catch (DirectoryNotFoundException)
			{
			}
		}

		public static void RestartGameAfterDownload(LocalModData[] toDeleteMods)
		{
			int num = (Plugin.IsDemo ? 2494960 : 1686940);
			int id = Process.GetCurrentProcess().Id;
			string gUID = ((BaseUnityPlugin)Plugin.plugin).Info.Metadata.GUID;
			string text = Path.Combine(MyCachePath, "mod_installer_deleter.bat");
			string fileName = Path.GetFileName(MyCachePath);
			string fileName2 = Path.GetFileName(DownloadedModsPath);
			string fileName3 = Path.GetFileName(OldPluginPath);
			string text2 = ";";
			if (toDeleteMods != null && toDeleteMods.Length != 0)
			{
				text2 = "\"" + string.Join("\";\"", toDeleteMods.Select((LocalModData e) => BaseUtils.GetRelativePath(e.Plugin.Location, Paths.PluginPath + "\\"))) + "\"";
			}
			File.WriteAllText(text, $"@echo off\r\ncd BepInEx\\cache\\{Path.GetFileName(MyCachePath)} 2>nul\r\ngoto :start\r\n\r\n:err_check\r\nif not \"%errorlevel%\" == \"0\" (\r\n\techo\\\r\n\techo \u001b[91mthere was an error code: %errorlevel%!\u001b[0m\r\n\tpause\r\n\texit 0\r\n)\r\nexit /b 0\r\n\r\nrem wait for game to finish\r\n:start\r\ntasklist /fi \"pid eq {id}\" /fo csv 2>nul | find /i \"{id}\" > nul\r\nif \"%ERRORLEVEL%\"==\"0\" goto start\r\nset \"errorlevel=0\"\r\n\r\ncall :err_check\r\n\r\nrem only copy mods if there is no copy already\r\nif not exist \"{fileName3}\" (\r\n\techo\\\r\n\techo \u001b[92mcopying old mods to new folder\u001b[0m\r\n\trobocopy ..\\..\\plugins\\ {fileName3}\\ /e /ndl /nc /ns\r\n\tcall :err_check\r\n)\r\n\r\necho\\\r\necho \u001b[92mdeleting not needed mods\u001b[0m\r\nfor %%f in ({text2}) do del ..\\..\\plugins\\%%f\r\n\r\ncall :err_check\r\n\r\necho\\\r\necho \u001b[92mcopying downloaded dlls into mod folder\u001b[0m\r\ncd ..\\..\\plugins\r\nfor /r %%f in (..\\cache\\{fileName}\\{fileName2}\\*.zip) do (\r\n\tmd \"%%~nf\"\r\n\tcd \"%%~nf\"\r\n\ttar -xvf \"%%f\"\r\n\tcd ..\r\n)\r\ncd ..\\cache\\{Path.GetFileName(MyCachePath)}\r\n\r\ncall :err_check\r\n\r\necho\\\r\necho \u001b[92mcleanup\u001b[0m\r\nrmdir /s /q \"{fileName2}\"\r\n\r\ncall :err_check\r\n\r\necho\\\r\necho \u001b[91mRestarting game!\u001b[0m\r\nstart \"\" steam://rungameid/{num}");
			Process.Start(text);
			Application.Quit();
		}

		public static void RestartGameAfterSync()
		{
			int num = (Plugin.IsDemo ? 2494960 : 1686940);
			int id = Process.GetCurrentProcess().Id;
			string text = Path.Combine(MyCachePath, "syncer.bat");
			File.WriteAllText(text, $"@echo off\r\n\r\nrem wait for game to finish\r\n:start\r\ntasklist /fi \"pid eq {id}\" /fo csv 2>nul | find /i \"{id}\" > nul\r\nif \"%ERRORLEVEL%\"==\"0\" goto start\r\nset \"errorlevel=0\"\r\n\r\ntimeout 2\r\n\r\necho\\\r\necho \u001b[91mRestarting game!\u001b[0m\r\nstart \"\" steam://rungameid/{num}");
			Process.Start(text);
			Application.Quit();
		}

		public static string GenerateField(string key)
		{
			return "almafa64>" + key;
		}

		public static Manifest GetManifest(PluginInfo plugin)
		{
			string text = plugin.Location;
			while ((text = Path.GetDirectoryName(text)).Length > Paths.PluginPath.Length)
			{
				string path = Path.Combine(text, "manifest.json");
				try
				{
					string text2 = File.ReadAllText(path);
					return new Manifest(JsonUtility.FromJson<ManifestJSON>(text2), text);
				}
				catch (FileNotFoundException)
				{
				}
				catch (Exception ex2)
				{
					Plugin.logger.LogError((object)ex2);
					break;
				}
			}
			return null;
		}

		public static Dictionary<string, HostConfigEntry[]> GetHostConfigs(string hostConfigListText)
		{
			//IL_006f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Expected O, but got Unknown
			Dictionary<string, HostConfigEntry[]> dictionary = new Dictionary<string, HostConfigEntry[]>();
			Dictionary<string, List<string[]>> dictionary2 = hostConfigListText.FromJson<Dictionary<string, List<string[]>>>();
			foreach (KeyValuePair<string, List<string[]>> item in dictionary2)
			{
				List<string[]> value = item.Value;
				HostConfigEntry[] array = new HostConfigEntry[value.Count];
				for (int i = 0; i < value.Count; i++)
				{
					string[] array2 = value[i];
					string[] array3 = array2[0].Split(new char[1] { '=' }, 2);
					array[i] = new HostConfigEntry(new ConfigDefinition(array3[0], array3[1]), Type.GetType(array2[1]), array2[2]);
				}
				dictionary.Add(item.Key, array);
			}
			return dictionary;
		}
	}
	internal class PanelMaker
	{
		internal static GameObject currentPanel;

		private static MainMenu currentMenu;

		public static GameObject GetTitle(GameObject panel = null)
		{
			return ((Component)GetPanel(panel).Find("Title")).gameObject;
		}

		public static GameObject GetInfo(GameObject panel = null)
		{
			return ((Component)GetPanel(panel).Find("Info")).gameObject;
		}

		public static GameObject GetTextArea(GameObject panel = null)
		{
			return ((Component)GetPanel(panel).Find("Text")).gameObject;
		}

		public static GameObject GetProgressBar(GameObject panel = null)
		{
			return ((Component)GetPanel(panel).Find("ProgressBar")).gameObject;
		}

		public static GameObject GetModList(GameObject panel = null)
		{
			return ((Component)GetPanel(panel).Find("NeededModList")).gameObject;
		}

		public static GameObject GetOkButton(GameObject panel = null)
		{
			return ((Component)GetPanel(panel).Find("OK Button")).gameObject;
		}

		public static GameObject GetCancelButton(GameObject panel = null)
		{
			return ((Component)GetPanel(panel).Find("Cancel Button")).gameObject;
		}

		public static Slider GetProgressBarComp(GameObject panel = null)
		{
			return GetProgressBar(panel).GetComponent<Slider>();
		}

		public static TextMeshProUGUI GetTitleText(GameObject panel = null)
		{
			return GetTitle(panel).GetComponent<TextMeshProUGUI>();
		}

		public static TextMeshProUGUI GetInfoText(GameObject panel = null)
		{
			return GetInfo(panel).GetComponent<TextMeshProUGUI>();
		}

		public static TextMeshProUGUI GetTextAreaText(GameObject panel = null)
		{
			return GetTextArea(panel).GetComponent<TextMeshProUGUI>();
		}

		public static Button GetOkButtonComp(GameObject panel = null)
		{
			return GetOkButton(panel).GetComponent<Button>();
		}

		public static Button GetCancelButtonComp(GameObject panel = null)
		{
			return GetCancelButton(panel).GetComponent<Button>();
		}

		private static Transform GetPanel(GameObject panel)
		{
			return (panel ?? currentPanel ?? throw new NullReferenceException("panel parameter and current panel is null")).transform;
		}

		private static GameObject CopyGeneric(GameObject genericPanel, string name)
		{
			GameObject val = Object.Instantiate<GameObject>(genericPanel);
			((Object)val).hideFlags = (HideFlags)1;
			Object.DontDestroyOnLoad((Object)(object)val);
			((Object)val).name = name;
			return val;
		}

		private static void SetupButtons(GameObject panel, UnityAction onOkClick)
		{
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Expected O, but got Unknown
			//IL_0050: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Expected O, but got Unknown
			((UnityEvent)GetOkButtonComp(panel).onClick).AddListener((UnityAction)delegate
			{
				close();
				UnityAction obj = onOkClick;
				if (obj != null)
				{
					obj.Invoke();
				}
			});
			((UnityEvent)GetCancelButtonComp(panel).onClick).AddListener((UnityAction)delegate
			{
				GameUtils.CancelSyncing();
				close();
			});
			void close()
			{
				Object.Destroy((Object)(object)panel);
				currentPanel = null;
				currentMenu.EnableAll();
			}
		}

		private static TMP_FontAsset GetDefaultFont()
		{
			return LocalizedText.localizationTable.GetFont((Language)0, false);
		}

		public static void MakeGenericPanel(ref GameObject genericPanel)
		{
			//IL_0078: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c2: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cb: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f3: Unknown result type (might be due to invalid IL or missing references)
			//IL_00fb: Unknown result type (might be due to invalid IL or missing references)
			//IL_0107: Unknown result type (might be due to invalid IL or missing references)
			//IL_010f: Unknown result type (might be due to invalid IL or missing references)
			//IL_013c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0176: Unknown result type (might be due to invalid IL or missing references)
			TextMeshProUGUI val = GetTitle(genericPanel).AddComponent<TextMeshProUGUI>();
			TextMeshProUGUI val2 = GetInfo(genericPanel).gameObject.AddComponent<TextMeshProUGUI>();
			TextMeshProUGUI val3 = ((Component)genericPanel.transform.Find("OK Button/Text (TMP)")).gameObject.AddComponent<TextMeshProUGUI>();
			TextMeshProUGUI val4 = ((Component)genericPanel.transform.Find("Cancel Button/Text (TMP)")).gameObject.AddComponent<TextMeshProUGUI>();
			TextMeshProUGUI val5 = GetTextArea(genericPanel).gameObject.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val).fontSize = 56f;
			((Graphic)val).color = Color.black;
			((TMP_Text)val).font = GetDefaultFont();
			((TMP_Text)val).alignment = (TextAlignmentOptions)513;
			((TMP_Text)val).fontStyle = (FontStyles)1;
			float fontSize = (((TMP_Text)val3).fontSize = 50f);
			((TMP_Text)val4).fontSize = fontSize;
			Color color = (((Graphic)val3).color = Color.black);
			((Graphic)val4).color = color;
			TMP_FontAsset font = (((TMP_Text)val3).font = GetDefaultFont());
			((TMP_Text)val4).font = font;
			TextAlignmentOptions alignment = (TextAlignmentOptions)514;
			((TMP_Text)val3).alignment = (TextAlignmentOptions)514;
			((TMP_Text)val4).alignment = alignment;
			FontStyles fontStyle = (FontStyles)1;
			((TMP_Text)val3).fontStyle = (FontStyles)1;
			((TMP_Text)val4).fontStyle = fontStyle;
			((TMP_Text)val3).text = "OK";
			((TMP_Text)val4).text = "Cancel";
			((TMP_Text)val2).fontSize = 50f;
			((Graphic)val2).color = Color.black;
			((TMP_Text)val2).font = GetDefaultFont();
			((TMP_Text)val2).alignment = (TextAlignmentOptions)513;
			((TMP_Text)val2).fontStyle = (FontStyles)0;
			((TMP_Text)val5).fontSize = 50f;
			((Graphic)val5).color = Color.black;
			((TMP_Text)val5).font = GetDefaultFont();
			((TMP_Text)val5).alignment = (TextAlignmentOptions)513;
			((TMP_Text)val5).fontStyle = (FontStyles)0;
		}

		public static GameObject MakeMissingModsPanel(GameObject genericPanel)
		{
			//IL_0058: 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)
			GameObject val = CopyGeneric(genericPanel, "MissingModsPanel");
			Object.Destroy((Object)(object)GetProgressBar(val));
			Object.Destroy((Object)(object)GetTextArea(val));
			TextMeshProUGUI val2 = ((Component)val.transform.Find("NeededModList/Viewport/Content/tmprow/Background/Label")).gameObject.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val2).fontSize = 50f;
			((Graphic)val2).color = Color32.op_Implicit(new Color32((byte)50, (byte)50, (byte)50, byte.MaxValue));
			((TMP_Text)val2).font = GetDefaultFont();
			((TMP_Text)val2).alignment = (TextAlignmentOptions)513;
			((Graphic)val2).raycastTarget = false;
			((TMP_Text)GetTitleText(val)).text = "Missing mods";
			((TMP_Text)GetInfoText(val)).text = "Install | Mod data";
			return val;
		}

		public static GameObject MakeNoSyncerPanel(GameObject genericPanel)
		{
			GameObject val = CopyGeneric(genericPanel, "HostMissingSyncer");
			Object.Destroy((Object)(object)GetProgressBar(val));
			Object.Destroy((Object)(object)GetTextArea(val));
			Object.Destroy((Object)(object)GetModList(val));
			((TMP_Text)GetTitleText(val)).text = "Warning!";
			((TMP_Text)GetInfoText(val)).text = "Host's missing BoplModSyncer";
			return val;
		}

		public static GameObject MakeInstallingPanel(GameObject genericPanel)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Expected O, but got Unknown
			GameObject val = CopyGeneric(genericPanel, "InstallingPanel");
			Object.Destroy((Object)(object)GetModList(val));
			TextMeshProUGUI val2 = ((Component)val.transform.Find("ProgressBar/Percentage")).gameObject.AddComponent<TextMeshProUGUI>();
			((TMP_Text)val2).fontSize = 50f;
			((Graphic)val2).color = Color.black;
			((TMP_Text)val2).font = GetDefaultFont();
			((TMP_Text)val2).alignment = (TextAlignmentOptions)513;
			((TMP_Text)GetTitleText(val)).text = "Downloading!";
			GameObject val3 = Object.Instantiate<GameObject>(((Component)GetOkButtonComp(val)).gameObject, val.transform);
			Button continueButton = val3.GetComponent<Button>();
			((UnityEvent)continueButton.onClick).AddListener((UnityAction)delegate
			{
				((Component)continueButton).gameObject.SetActive(false);
			});
			val3.SetActive(false);
			((Object)val3).name = "Continue Button";
			((TMP_Text)((Component)val3.transform.Find("Text (TMP)")).GetComponent<TextMeshProUGUI>()).text = "Continue";
			return val;
		}

		public static GameObject MakeRestartPanel(GameObject genericPanel)
		{
			GameObject val = CopyGeneric(genericPanel, "RestartPanel");
			Object.Destroy((Object)(object)GetProgressBar(val));
			Object.Destroy((Object)(object)GetTextArea(val));
			Object.Destroy((Object)(object)GetModList(val));
			((TMP_Text)GetTitleText(val)).text = "Warning!";
			((TMP_Text)GetInfoText(val)).text = "Game will be restarted!";
			return val;
		}

		public static GameObject InstantiatePanel(GameObject panelToInstantiate, UnityAction onOkClick = null)
		{
			//IL_0035: 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)
			if ((Object)(object)currentPanel != (Object)null)
			{
				throw new Exception("'" + ((Object)currentPanel).name + "' panel is already open!");
			}
			Transform canvas = PanelUtils.GetCanvas();
			Scene activeScene = SceneManager.GetActiveScene();
			if (((Scene)(ref activeScene)).name == "MainMenu")
			{
				MainMenu[] componentsInChildren = ((Component)canvas).GetComponentsInChildren<MainMenu>();
				foreach (MainMenu val in componentsInChildren)
				{
					if (val.IsEnabled)
					{
						val.DisableAll();
						currentMenu = val;
						break;
					}
				}
			}
			currentPanel = Object.Instantiate<GameObject>(panelToInstantiate, canvas);
			SetupButtons(currentPanel, onOkClick);
			return currentPanel;
		}
	}
	internal class PanelUtils
	{
		public static Transform GetCanvas()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0006: 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_0082: Expected O, but got Unknown
			Scene activeScene = SceneManager.GetActiveScene();
			string name = ((Scene)(ref activeScene)).name;
			if (name == "MainMenu")
			{
				return GameObject.Find("Canvas (1)").transform;
			}
			if (name.Contains("Select"))
			{
				return GameObject.Find("Canvas").transform;
			}
			GameObject val = GameObject.Find("Canvas");
			if ((Object)(object)val != (Object)null)
			{
				return val.transform;
			}
			val = new GameObject("Canvas");
			val.AddComponent<Canvas>();
			return val.transform;
		}
	}
}