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