Decompiled source of CrashoutCrewLobbyEnforcer v1.0.2

CrashoutCrew_LobbyEnforcer.dll

Decompiled 20 hours ago
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.Threading.Tasks;
using Aggro.Core.Networking;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
using Microsoft.CodeAnalysis;
using Mirror;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: IgnoresAccessChecksTo("aggro.core")]
[assembly: IgnoresAccessChecksTo("febjam")]
[assembly: AssemblyCompany("CrashoutCrew_LobbyEnforcer")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+141c9618b809295e3f91c216c892cfee5b4a9724")]
[assembly: AssemblyProduct("CrashoutCrew_LobbyEnforcer")]
[assembly: AssemblyTitle("CrashoutCrew_LobbyEnforcer")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.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 TemplatePlugin
{
	internal static class TemplatePluginInfo
	{
		public const string PLUGIN_GUID = "crashout_crew_lobby_enforcer";

		public const string PLUGIN_NAME = "CrashoutCrew_LobbyEnforcer";

		public const string GAME_NAME = "CrashoutCrew";
	}
}
namespace CrashoutCrew_LobbyEnforcer
{
	public static class Enforcer
	{
		private static readonly Dictionary<string, ModEntry> EnforcedMods = new Dictionary<string, ModEntry>();

		public static event Action? ConnectionToServerValidated;

		public static event Action<NetworkConnectionToClient>? ServerOnClientValidated;

		internal static void NotifyConnectionToServerValidated()
		{
			Enforcer.ConnectionToServerValidated?.Invoke();
		}

		internal static void NotifyClientValidatedByServer(NetworkConnectionToClient conn)
		{
			Enforcer.ServerOnClientValidated?.Invoke(conn);
		}

		public static List<ModEntry> GetRegisteredMods()
		{
			return EnforcedMods.Values.ToList();
		}

		public static void Enforce(string modId, string modVersion, EnforcementDirection direction)
		{
			EnforcedMods[modId] = new ModEntry
			{
				modId = modId,
				modVersion = modVersion,
				direction = direction
			};
		}

		public static void Enforce(string modId, string modVersion, EnforcementDirection direction, string data)
		{
			EnforcedMods[modId] = new ModEntry
			{
				modId = modId,
				modVersion = modVersion,
				direction = direction,
				data = data
			};
		}

		public static void UpdateEnforceData(string modId, string data)
		{
			if (EnforcedMods.TryGetValue(modId, out ModEntry value))
			{
				value.data = data;
			}
		}

		internal static bool ValidateRemote(List<ModEntry> remoteMods, bool isRemoteServer, out string failureReason)
		{
			failureReason = string.Empty;
			Dictionary<string, ModEntry> dictionary = new Dictionary<string, ModEntry>();
			foreach (ModEntry remoteMod in remoteMods)
			{
				dictionary[remoteMod.modId] = new ModEntry
				{
					modId = remoteMod.modId,
					modVersion = remoteMod.modVersion,
					direction = remoteMod.direction,
					data = remoteMod.data
				};
			}
			List<string> list = new List<string>();
			List<(string, string, string)> list2 = new List<(string, string, string)>();
			List<(string, string, string)> list3 = new List<(string, string, string)>();
			foreach (ModEntry value2 in EnforcedMods.Values)
			{
				bool flag2;
				if (isRemoteServer)
				{
					EnforcementDirection direction = value2.direction;
					bool flag = ((direction == EnforcementDirection.Server || direction == EnforcementDirection.Both) ? true : false);
					flag2 = flag;
				}
				else
				{
					EnforcementDirection direction = value2.direction;
					bool flag = (uint)(direction - 1) <= 1u;
					flag2 = flag;
				}
				if (!flag2)
				{
					continue;
				}
				if (!dictionary.TryGetValue(value2.modId, out var value))
				{
					list.Add(value2.modId);
					continue;
				}
				if (value.modVersion != value2.modVersion)
				{
					list2.Add((value2.modId, value2.modVersion, value.modVersion));
				}
				if (value.data != value2.data)
				{
					list3.Add((value2.modId, value2.data, value.data));
				}
			}
			if (list.Count <= 0)
			{
				return true;
			}
			failureReason = list.Aggregate(failureReason, (string text, string missingMod) => text + "Missing required mod: " + missingMod + "\n");
			failureReason = list2.Aggregate<(string, string, string), string>(failureReason, (string text, (string modId, string localVersion, string remoteVersion) mismatchedMod) => text + "Version mismatch for " + mismatchedMod.modId + ". Local: " + mismatchedMod.localVersion + ", Remote: " + mismatchedMod.remoteVersion + "\n");
			failureReason = list3.Aggregate<(string, string, string), string>(failureReason, (string text, (string modId, string localData, string remoteData) mismatchedMod) => text + "Data mismatch for " + mismatchedMod.modId + ". Local: " + mismatchedMod.localData + ", Remote: " + mismatchedMod.remoteData + "\n");
			return false;
		}

		internal static bool RequiresRemoteEnforcer(bool isRemoteServer)
		{
			return EnforcedMods.Values.Any(delegate(ModEntry local)
			{
				bool flag = isRemoteServer;
				if (flag)
				{
					EnforcementDirection direction = local.direction;
					bool flag2 = ((direction == EnforcementDirection.Server || direction == EnforcementDirection.Both) ? true : false);
					flag = flag2;
				}
				bool flag3 = flag;
				if (!flag3)
				{
					bool flag2 = !isRemoteServer;
					if (flag2)
					{
						EnforcementDirection direction = local.direction;
						bool flag4 = (uint)(direction - 1) <= 1u;
						flag2 = flag4;
					}
					flag3 = flag2;
				}
				return flag3;
			});
		}
	}
	[BepInPlugin("crashout_crew_lobby_enforcer", "CrashoutCrew_LobbyEnforcer", "1.0.2")]
	public class PluginMain : BaseUnityPlugin
	{
		public const string GameName = "CrashoutCrew";

		private const string Version = "1.0.2";

		private readonly Harmony _harmony = new Harmony("crashout_crew_lobby_enforcer");

		public static ManualLogSource? logger;

		private void Awake()
		{
			logger = ((BaseUnityPlugin)this).Logger;
			_harmony.PatchAll();
			Object.DontDestroyOnLoad((Object)(object)((Component)this).gameObject);
		}
	}
	public enum EnforcementDirection
	{
		Server,
		Clients,
		Both
	}
	public class ModEntry
	{
		public string modId = "";

		public string modVersion = "";

		public EnforcementDirection direction = EnforcementDirection.Both;

		public string data = "";

		public static string SerializeList(List<ModEntry> mods)
		{
			if (mods.Count == 0)
			{
				return string.Empty;
			}
			using MemoryStream memoryStream = new MemoryStream();
			using BinaryWriter binaryWriter = new BinaryWriter(memoryStream);
			binaryWriter.Write(mods.Count);
			foreach (ModEntry mod in mods)
			{
				binaryWriter.Write(mod.modId);
				binaryWriter.Write(mod.modVersion);
				binaryWriter.Write((int)mod.direction);
				binaryWriter.Write(mod.data);
			}
			return Convert.ToBase64String(memoryStream.ToArray());
		}

		public static List<ModEntry> DeserializeList(string base64)
		{
			List<ModEntry> list = new List<ModEntry>();
			if (string.IsNullOrEmpty(base64))
			{
				return list;
			}
			using MemoryStream input = new MemoryStream(Convert.FromBase64String(base64));
			using BinaryReader binaryReader = new BinaryReader(input);
			int num = binaryReader.ReadInt32();
			for (int i = 0; i < num; i++)
			{
				list.Add(new ModEntry
				{
					modId = binaryReader.ReadString(),
					modVersion = binaryReader.ReadString(),
					direction = (EnforcementDirection)binaryReader.ReadInt32(),
					data = binaryReader.ReadString()
				});
			}
			return list;
		}
	}
}
namespace CrashoutCrew_LobbyEnforcer.Patches
{
	[HarmonyPatch(typeof(AggroNetworkManager))]
	public class AggroNetworkManager_Patches
	{
		private const string PayloadPrefix = "\0LE:";

		private const string HostHandshakeMarker = "LE_HOST_HANDSHAKE";

		private static bool _serverHandshakeReceived;

		private static bool _serverHandshakeValid;

		[HarmonyPatch("OnStartClient")]
		[HarmonyPostfix]
		public static void OnStartClient_Postfix()
		{
			_serverHandshakeReceived = false;
			_serverHandshakeValid = false;
		}

		[HarmonyPatch("OnServerPlayerJoined")]
		[HarmonyPrefix]
		public static bool OnServerPlayerJoinedPrefix(AggroNetworkManager __instance, NetworkConnectionToClient conn, NetMsgServerPlayerJoined msg)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			string text = msg.playerName ?? string.Empty;
			string playerName = text;
			bool flag = false;
			int num = text.IndexOf("\0LE:", StringComparison.Ordinal);
			if (num >= 0)
			{
				playerName = text.Substring(0, num);
				string text2 = text;
				int num2 = num + "\0LE:".Length;
				string @base = text2.Substring(num2, text2.Length - num2);
				flag = true;
				try
				{
					if (!Enforcer.ValidateRemote(ModEntry.DeserializeList(@base), isRemoteServer: false, out string failureReason))
					{
						PluginMain.logger.LogWarning((object)("[LobbyEnforcer] Rejecting client " + conn.address + ":\n" + failureReason));
						((NetworkConnection)conn).Disconnect();
						return false;
					}
				}
				catch (Exception ex)
				{
					PluginMain.logger.LogError((object)("[LobbyEnforcer] Failed to parse client payload:\n" + ex.Message));
					((NetworkConnection)conn).Disconnect();
					return false;
				}
			}
			if (!flag && Enforcer.RequiresRemoteEnforcer(isRemoteServer: false))
			{
				PluginMain.logger.LogWarning((object)("[LobbyEnforcer] Rejecting VANILLA client " + conn.address + ": Server requires client mods."));
				((NetworkConnection)conn).Disconnect();
				return false;
			}
			msg.playerName = playerName;
			return true;
		}

		[HarmonyPatch("HandleClientConnectCo")]
		[HarmonyPrefix]
		public static bool HandleClientConnectCoPrefix(AggroNetworkManager __instance, NetworkConnectionToClient conn, ref IEnumerator __result)
		{
			__result = HandleClientConnectWithEnforcement(__instance, conn);
			return false;
		}

		private static IEnumerator HandleClientConnectWithEnforcement(AggroNetworkManager manager, NetworkConnectionToClient conn)
		{
			if (!(conn is LocalConnectionToClient))
			{
				((NetworkConnection)conn).Send<NetMsgGameSettings>(new NetMsgGameSettings
				{
					versionGuid = new Guid(Application.buildGUID)
				}, 0);
				string text = ModEntry.SerializeList(Enforcer.GetRegisteredMods());
				((NetworkConnection)conn).Send<NetMsgPlayerJoined>(new NetMsgPlayerJoined
				{
					playerName = "LE_HOST_HANDSHAKE\0LE:" + text
				}, 0);
			}
			while (!((NetworkConnection)conn).isReady)
			{
				yield return null;
			}
			AggroNetworkManager._behaviourQuery.Run();
			for (int i = 0; i < AggroNetworkManager._behaviourQuery.count; i++)
			{
				AggroNetworkManager._behaviourQuery[i].ServerClientConnected(conn);
			}
			int num = -1;
			for (int j = 0; j < AggroNetworkManager._orderedConnections.Count; j++)
			{
				if (AggroNetworkManager._orderedConnections[j] == null)
				{
					num = j;
					break;
				}
			}
			if (num < 0)
			{
				num = AggroNetworkManager._orderedConnections.Count;
				AggroNetworkManager._orderedConnections.Add(null);
			}
			AggroNetworkManager._orderedConnections[num] = conn;
			NetworkAggroManagerBase<LobbyManager>.instance.ServerAddPlayer(conn, num);
			NetworkAggroManagerBase<NetworkPlayerManager>.instance.ServerPlayerJoined(conn);
			Enforcer.NotifyClientValidatedByServer(conn);
		}

		[HarmonyPatch("OnPlayerJoined")]
		[HarmonyPrefix]
		public static bool OnPlayerJoinedPrefix(NetMsgPlayerJoined msg)
		{
			//IL_0000: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e4: 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_001c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0034: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a4: Unknown result type (might be due to invalid IL or missing references)
			if (msg.playerName == null || !msg.playerName.StartsWith("LE_HOST_HANDSHAKE"))
			{
				return true;
			}
			int num = msg.playerName.IndexOf("\0LE:", StringComparison.Ordinal);
			if (num < 0)
			{
				return false;
			}
			string playerName = msg.playerName;
			int num2 = num + "\0LE:".Length;
			string @base = playerName.Substring(num2, playerName.Length - num2);
			_serverHandshakeReceived = true;
			try
			{
				_serverHandshakeValid = Enforcer.ValidateRemote(ModEntry.DeserializeList(@base), isRemoteServer: true, out string failureReason);
				if (!_serverHandshakeValid)
				{
					PluginMain.logger.LogError((object)("[LobbyEnforcer] Host failed mod validation:\n" + failureReason));
					PlayerMessageManager.QueueMessage("Host failed mod validation:\n" + failureReason, true, true, false, "", (Action)null, (Action)null);
				}
			}
			catch (Exception ex)
			{
				PluginMain.logger.LogError((object)("[LobbyEnforcer] Failed to parse host payload:\n" + ex.Message));
				PlayerMessageManager.QueueMessage("Failed to parse host payload:\n" + ex.Message, true, true, false, "", (Action)null, (Action)null);
				_serverHandshakeValid = false;
			}
			return false;
		}

		[HarmonyPatch("StartClientAsync")]
		[HarmonyPostfix]
		public static async Task<ClientConnectionResult> StartClientAsyncPostfix(Task<ClientConnectionResult> originalTask)
		{
			ClientConnectionResult result = await originalTask;
			if (!((ClientConnectionResult)(ref result)).isSuccess)
			{
				return result;
			}
			float timeout = 1f;
			while (!_serverHandshakeReceived && timeout > 0f)
			{
				timeout -= Time.deltaTime;
				await Task.Yield();
			}
			if (_serverHandshakeReceived)
			{
				if (_serverHandshakeValid)
				{
					Enforcer.NotifyConnectionToServerValidated();
					return result;
				}
				AggroNetworkManager.Disconnect();
				return ClientConnectionResult.Failed();
			}
			if (!Enforcer.RequiresRemoteEnforcer(isRemoteServer: true))
			{
				return result;
			}
			PluginMain.logger.LogError((object)"[LobbyEnforcer] Server is missing required mods or LobbyEnforcer.");
			PlayerMessageManager.QueueMessage("Server is missing required mods or LobbyEnforcer.", true, true, false, "", (Action)null, (Action)null);
			AggroNetworkManager.Disconnect();
			return ClientConnectionResult.Failed();
		}
	}
	[HarmonyPatch]
	public static class NetworkClient_Patches
	{
		[HarmonyTargetMethod]
		public static MethodBase TargetMethod()
		{
			return typeof(NetworkClient).GetMethod("Send", BindingFlags.Static | BindingFlags.Public)?.MakeGenericMethod(typeof(NetMsgServerPlayerJoined));
		}

		[HarmonyPrefix]
		public static void Prefix(ref NetMsgServerPlayerJoined message)
		{
			string text = ModEntry.SerializeList(Enforcer.GetRegisteredMods());
			message.playerName = (message.playerName ?? string.Empty) + "\0LE:" + text;
		}
	}
}
namespace System.Runtime.CompilerServices
{
	[AttributeUsage(AttributeTargets.Assembly, AllowMultiple = true)]
	internal sealed class IgnoresAccessChecksToAttribute : Attribute
	{
		internal IgnoresAccessChecksToAttribute(string assemblyName)
		{
		}
	}
}