Decompiled source of FakePlayers v1.1.4

FakePlayers.dll

Decompiled a day ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.Net;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using DooDesch;
using FakePlayers;
using FakePlayers.Config;
using FakePlayers.Managers;
using FakePlayers.Patches;
using HarmonyLib;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using ReluNetwork.ConstEnum;
using ReluProtocol;
using ReluProtocol.C2S;
using ReluProtocol.Enum;
using Steamworks;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(Core), "FakePlayers", "1.1.4", "DooDesch", null)]
[assembly: MelonGame("ReLUGames", "MIMESIS")]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("DooDesch")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyCopyright("Copyright © DooDesch")]
[assembly: AssemblyFileVersion("1.1.4.0")]
[assembly: AssemblyInformationalVersion("1.1.4+45698134a396c926a122755b6fb6724bcabac55f")]
[assembly: AssemblyProduct("FakePlayers")]
[assembly: AssemblyTitle("FakePlayers")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: AssemblyMetadata("AI_Assisted_Creation", "This assembly was partially created with the assistance of Generative AI.")]
[assembly: AssemblyMetadata("AI_Model_Vendor", "Anthropic/OpenAI")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.4.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 DooDesch
{
	internal sealed class HostSettings
	{
		[HarmonyPatch(typeof(SteamInviteDispatcher), "OnLobbyCreated")]
		private static class CreatedPatch
		{
			private static void Postfix(LobbyCreated_t callback)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_0001: Unknown result type (might be due to invalid IL or missing references)
				//IL_0007: Invalid comparison between Unknown and I4
				//IL_001e: Unknown result type (might be due to invalid IL or missing references)
				//IL_0024: Unknown result type (might be due to invalid IL or missing references)
				if ((int)callback.m_eResult != 1)
				{
					return;
				}
				foreach (HostSettings instance in Instances)
				{
					instance.Begin(new CSteamID(callback.m_ulSteamIDLobby), host: true);
				}
			}
		}

		[HarmonyPatch(typeof(SteamInviteDispatcher), "OnLobbyEntered")]
		private static class EnteredPatch
		{
			private static void Prefix(LobbyEnter_t callback)
			{
				//IL_0000: Unknown result type (might be due to invalid IL or missing references)
				//IL_000c: Unknown result type (might be due to invalid IL or missing references)
				//IL_0043: Unknown result type (might be due to invalid IL or missing references)
				//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 (callback.m_EChatRoomEnterResponse != 1)
				{
					return;
				}
				CSteamID val = default(CSteamID);
				((CSteamID)(ref val))..ctor(callback.m_ulSteamIDLobby);
				foreach (HostSettings instance in Instances)
				{
					if (!instance.isHost || !(instance.lobby == val))
					{
						instance.Begin(val, host: false);
					}
				}
			}
		}

		[HarmonyPatch(typeof(SteamMatchmaking), "LeaveLobby")]
		private static class LeavePatch
		{
			private static void Prefix(CSteamID steamIDLobby)
			{
				//IL_0016: Unknown result type (might be due to invalid IL or missing references)
				//IL_001b: Unknown result type (might be due to invalid IL or missing references)
				foreach (HostSettings instance in Instances)
				{
					if (instance.lobby == steamIDLobby)
					{
						instance.Reset();
					}
				}
			}
		}

		[HarmonyPatch(typeof(MainMenu), "Start")]
		private static class MenuPatch
		{
			private static void Prefix()
			{
				foreach (HostSettings instance in Instances)
				{
					instance.Reset();
				}
			}
		}

		private static readonly List<HostSettings> Instances = new List<HostSettings>();

		private static Callback<LobbyDataUpdate_t> dataCallback;

		private static float nextPoll;

		private readonly string modId;

		private readonly string lobbyKey;

		private readonly int schemaVersion;

		private readonly List<ISetting> settings = new List<ISetting>();

		private Dictionary<string, object> snapshot;

		private CSteamID lobby;

		private CSteamID owner;

		private bool isHost;

		private bool hostCreated;

		private bool authorityValid;

		private bool ownerChanged;

		private bool published;

		private string hostPayload;

		private string warningReason;

		private int warningRepeats;

		internal bool IsInSession => lobby != CSteamID.Nil;

		internal bool IsHost
		{
			get
			{
				if (isHost)
				{
					return authorityValid;
				}
				return false;
			}
		}

		internal event Action EffectiveValuesChanged;

		internal HostSettings(string modId, int schemaVersion = 1)
		{
			if (string.IsNullOrWhiteSpace(modId))
			{
				throw new ArgumentException("A mod ID is required.", "modId");
			}
			if (schemaVersion < 1)
			{
				throw new ArgumentOutOfRangeException("schemaVersion");
			}
			this.modId = modId;
			this.schemaVersion = schemaVersion;
			lobbyKey = "doodesch." + modId.ToLowerInvariant() + ".settings.v" + schemaVersion;
			if (Encoding.UTF8.GetByteCount(lobbyKey) >= 255)
			{
				throw new ArgumentException("The mod ID exceeds the Steam lobby key limit.", "modId");
			}
			foreach (HostSettings instance in Instances)
			{
				if (instance.lobbyKey == lobbyKey)
				{
					throw new ArgumentException("The mod already has a settings snapshot.", "modId");
				}
			}
			Instances.Add(this);
		}

		internal HostSetting<T> Register<T>(MelonPreferences_Entry<T> entry, T vanillaValue, Func<T, bool> validate = null)
		{
			//IL_005b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0060: Unknown result type (might be due to invalid IL or missing references)
			if (entry == null)
			{
				throw new ArgumentNullException("entry");
			}
			if (string.IsNullOrWhiteSpace(((MelonPreferences_Entry)entry).Identifier))
			{
				throw new ArgumentException("A setting identifier is required.", "entry");
			}
			if (((MelonPreferences_Entry)entry).Identifier.IndexOfAny(new char[3] { '\n', '\r', '\t' }) >= 0)
			{
				throw new ArgumentException("Setting identifiers cannot contain wire delimiters.", "entry");
			}
			if (lobby != CSteamID.Nil)
			{
				throw new InvalidOperationException("Register settings before entering a lobby.");
			}
			if (typeof(T) != typeof(bool) && typeof(T) != typeof(int) && typeof(T) != typeof(float))
			{
				throw new NotSupportedException("Host settings support bool, int and float values.");
			}
			foreach (ISetting setting in settings)
			{
				if (setting.Key == ((MelonPreferences_Entry)entry).Identifier)
				{
					throw new ArgumentException("Duplicate setting: " + ((MelonPreferences_Entry)entry).Identifier, "entry");
				}
			}
			HostSetting<T> hostSetting = new HostSetting<T>(this, entry, vanillaValue, validate);
			if (!hostSetting.IsValid(vanillaValue))
			{
				throw new ArgumentException("The vanilla value is invalid: " + ((MelonPreferences_Entry)entry).Identifier, "vanillaValue");
			}
			settings.Add(hostSetting);
			return hostSetting;
		}

		internal T GetValue<T>(string key, T localValue, T vanillaValue)
		{
			//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)
			if (lobby == CSteamID.Nil)
			{
				return localValue;
			}
			if (snapshot == null || !snapshot.TryGetValue(key, out var value))
			{
				return vanillaValue;
			}
			return (T)value;
		}

		internal static void Update()
		{
			//IL_0033: Unknown result type (might be due to invalid IL or missing references)
			//IL_0038: Unknown result type (might be due to invalid IL or missing references)
			if (Time.realtimeSinceStartup < nextPoll)
			{
				return;
			}
			nextPoll = Time.realtimeSinceStartup + 1f;
			foreach (HostSettings instance in Instances)
			{
				if (instance.lobby != CSteamID.Nil)
				{
					instance.Refresh();
				}
			}
		}

		private void Begin(CSteamID newLobby, bool host)
		{
			//IL_000f: Unknown result type (might be due to invalid IL or missing references)
			//IL_0014: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_003d: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0075: Unknown result type (might be due to invalid IL or missing references)
			//IL_007a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0081: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0090: Unknown result type (might be due to invalid IL or missing references)
			//IL_0095: Unknown result type (might be due to invalid IL or missing references)
			//IL_0097: Unknown result type (might be due to invalid IL or missing references)
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00bf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b1: Unknown result type (might be due to invalid IL or missing references)
			if (settings.Count == 0 || (lobby == newLobby && (!host || hostCreated)))
			{
				return;
			}
			if (lobby != newLobby)
			{
				Reset();
			}
			lobby = newLobby;
			isHost = false;
			hostCreated = host;
			authorityValid = false;
			ownerChanged = false;
			snapshot = null;
			published = false;
			hostPayload = null;
			owner = CSteamID.Nil;
			try
			{
				owner = SteamMatchmaking.GetLobbyOwner(lobby);
				CSteamID steamID = SteamUser.GetSteamID();
				if (owner == CSteamID.Nil || (host ? (owner != steamID) : (owner == steamID)))
				{
					owner = CSteamID.Nil;
					Warn("Lobby owner does not identify the expected host; using vanilla settings.");
					Changed();
					return;
				}
				isHost = host;
				authorityValid = true;
				if (host)
				{
					CaptureHostSnapshot();
				}
				else
				{
					CaptureClientSnapshot();
				}
			}
			catch (Exception ex)
			{
				Warn("Initial lobby settings failed; vanilla settings are frozen for this session: " + ex.GetType().Name + ": " + ex.Message);
			}
			Refresh();
			Changed();
		}

		private static void EnsureCallback()
		{
			if (dataCallback == null)
			{
				dataCallback = Callback<LobbyDataUpdate_t>.Create((DispatchDelegate<LobbyDataUpdate_t>)OnLobbyDataUpdated);
			}
		}

		private static void OnLobbyDataUpdated(LobbyDataUpdate_t callback)
		{
			//IL_0020: Unknown result type (might be due to invalid IL or missing references)
			//IL_0028: Unknown result type (might be due to invalid IL or missing references)
			//IL_002e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0036: Unknown result type (might be due to invalid IL or missing references)
			foreach (HostSettings instance in Instances)
			{
				if (instance.lobby.m_SteamID == callback.m_ulSteamIDLobby && callback.m_ulSteamIDMember == callback.m_ulSteamIDLobby)
				{
					if (callback.m_bSuccess == 0)
					{
						instance.Warn("Steam lobby data update failed; frozen session settings are retained.");
					}
					else
					{
						instance.Refresh();
					}
				}
			}
		}

		private void CaptureHostSnapshot()
		{
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
			StringBuilder stringBuilder = new StringBuilder(schemaVersion.ToString(CultureInfo.InvariantCulture));
			for (int i = 0; i < settings.Count; i++)
			{
				ISetting setting = settings[i];
				object localValue = setting.LocalValue;
				if (!setting.IsValid(localValue))
				{
					throw new FormatException("Invalid local setting: " + setting.Key);
				}
				dictionary.Add(setting.Key, localValue);
				stringBuilder.Append('\n').Append(setting.Key).Append('\t')
					.Append(Format(localValue));
			}
			string s = stringBuilder.ToString();
			if (Encoding.UTF8.GetByteCount(s) + 1 > 8192)
			{
				throw new FormatException("Settings snapshot exceeds the Steam metadata limit.");
			}
			hostPayload = s;
			snapshot = dictionary;
		}

		private unsafe void CaptureClientSnapshot()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0055: Unknown result type (might be due to invalid IL or missing references)
			//IL_005a: Unknown result type (might be due to invalid IL or missing references)
			string lobbyData = SteamMatchmaking.GetLobbyData(lobby, lobbyKey);
			if (string.IsNullOrEmpty(lobbyData))
			{
				Warn("Host snapshot is absent; vanilla settings are frozen for this session.");
				return;
			}
			snapshot = ParseSnapshot(lobbyData);
			string[] obj = new string[5] { "[", modId, "] Host settings snapshot frozen for lobby ", null, null };
			CSteamID val = lobby;
			obj[3] = ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString();
			obj[4] = ".";
			MelonLogger.Msg(string.Concat(obj));
		}

		private void Refresh()
		{
			//IL_0006: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0010: Unknown result type (might be due to invalid IL or missing references)
			//IL_0040: Unknown result type (might be due to invalid IL or missing references)
			//IL_0041: Unknown result type (might be due to invalid IL or missing references)
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: 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)
			//IL_004f: Unknown result type (might be due to invalid IL or missing references)
			//IL_002b: Unknown result type (might be due to invalid IL or missing references)
			//IL_002d: Unknown result type (might be due to invalid IL or missing references)
			try
			{
				EnsureCallback();
				CSteamID lobbyOwner = SteamMatchmaking.GetLobbyOwner(lobby);
				if (ownerChanged || owner == CSteamID.Nil || lobbyOwner != owner)
				{
					authorityValid = false;
					if (lobbyOwner != CSteamID.Nil && lobbyOwner != owner)
					{
						ownerChanged = true;
					}
					Warn("Lobby owner changed or is unavailable; authority revoked and frozen session settings retained.");
					return;
				}
				bool num = !authorityValid;
				authorityValid = true;
				if (num)
				{
					MelonLogger.Msg("[" + modId + "] Pinned lobby authority recovered; frozen settings retained.");
				}
				if (isHost && hostPayload != null)
				{
					if (published)
					{
						Recover();
					}
					else
					{
						Publish();
					}
				}
			}
			catch (Exception ex)
			{
				authorityValid = false;
				Warn("Refresh lobby settings failed; frozen session settings retained: " + ex.GetType().Name + ": " + ex.Message);
			}
		}

		private unsafe void Publish()
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			//IL_0062: Unknown result type (might be due to invalid IL or missing references)
			//IL_001f: Unknown result type (might be due to invalid IL or missing references)
			//IL_00d8: Unknown result type (might be due to invalid IL or missing references)
			//IL_00dd: Unknown result type (might be due to invalid IL or missing references)
			//IL_0086: Unknown result type (might be due to invalid IL or missing references)
			//IL_008b: Unknown result type (might be due to invalid IL or missing references)
			//IL_003e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0043: Unknown result type (might be due to invalid IL or missing references)
			CSteamID val;
			if (SteamMatchmaking.GetLobbyData(lobby, lobbyKey) != hostPayload && !SteamMatchmaking.SetLobbyData(lobby, lobbyKey, hostPayload))
			{
				val = lobby;
				Warn("SetLobbyData rejected the settings snapshot for lobby " + ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString() + ".");
				return;
			}
			if (SteamMatchmaking.GetLobbyData(lobby, lobbyKey) != hostPayload)
			{
				val = lobby;
				Warn("Settings snapshot readback differs for lobby " + ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString() + ".");
				return;
			}
			published = true;
			Recover();
			string[] obj = new string[5] { "[", modId, "] Settings snapshot confirmed by local lobby readback for ", null, null };
			val = lobby;
			obj[3] = ((object)(*(CSteamID*)(&val))/*cast due to .constrained prefix*/).ToString();
			obj[4] = ".";
			MelonLogger.Msg(string.Concat(obj));
		}

		private Dictionary<string, object> ParseSnapshot(string payload)
		{
			if (Encoding.UTF8.GetByteCount(payload) + 1 > 8192)
			{
				throw new FormatException("Settings snapshot exceeds the Steam metadata limit.");
			}
			string[] array = payload.Split('\n');
			if (array[0] != schemaVersion.ToString(CultureInfo.InvariantCulture))
			{
				throw new FormatException("Settings snapshot schema mismatch.");
			}
			if (array.Length != settings.Count + 1)
			{
				throw new FormatException("Settings snapshot has missing or extra keys.");
			}
			Dictionary<string, object> dictionary = new Dictionary<string, object>(StringComparer.Ordinal);
			for (int i = 1; i < array.Length; i++)
			{
				string text = array[i];
				int num = text.IndexOf('\t');
				if (num <= 0 || num != text.LastIndexOf('\t') || text.IndexOf('\r') >= 0)
				{
					throw new FormatException("Settings snapshot has an invalid entry delimiter.");
				}
				string key = text.Substring(0, num);
				if (dictionary.ContainsKey(key))
				{
					throw new FormatException("Settings snapshot has a duplicate or missing key.");
				}
				ISetting setting = settings.Find((ISetting candidate) => candidate.Key == key);
				if (setting == null)
				{
					throw new FormatException("Settings snapshot has an unknown key: " + key);
				}
				dictionary.Add(key, setting.Parse(text.Substring(num + 1)));
			}
			return dictionary;
		}

		private static string Format(object value)
		{
			if (value is float num)
			{
				return num.ToString("R", CultureInfo.InvariantCulture);
			}
			if (value is bool)
			{
				if (!(bool)value)
				{
					return "false";
				}
				return "true";
			}
			return ((int)value).ToString(CultureInfo.InvariantCulture);
		}

		private void Warn(string reason)
		{
			if (warningReason == reason)
			{
				warningRepeats++;
				return;
			}
			if (warningRepeats > 0)
			{
				MelonLogger.Warning("[" + modId + "] Previous settings warning repeated " + warningRepeats + " times.");
			}
			warningReason = reason;
			warningRepeats = 0;
			MelonLogger.Warning("[" + modId + "] " + reason);
		}

		private void Recover()
		{
			if (warningReason != null)
			{
				MelonLogger.Msg("[" + modId + "] Lobby settings recovered after " + (warningRepeats + 1) + " warning occurrences.");
				warningReason = null;
				warningRepeats = 0;
			}
		}

		private void Changed()
		{
			if (this.EffectiveValuesChanged == null)
			{
				return;
			}
			Delegate[] invocationList = this.EffectiveValuesChanged.GetInvocationList();
			for (int i = 0; i < invocationList.Length; i++)
			{
				Action action = (Action)invocationList[i];
				try
				{
					action();
				}
				catch (Exception ex)
				{
					MelonLogger.Error("[" + modId + "] Apply effective settings failed.", ex);
				}
			}
		}

		private void Reset()
		{
			//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_0011: Unknown result type (might be due to invalid IL or missing references)
			//IL_0016: 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_0021: Unknown result type (might be due to invalid IL or missing references)
			bool num = lobby != CSteamID.Nil;
			lobby = CSteamID.Nil;
			owner = CSteamID.Nil;
			isHost = false;
			hostCreated = false;
			authorityValid = false;
			ownerChanged = false;
			published = false;
			snapshot = null;
			hostPayload = null;
			warningReason = null;
			warningRepeats = 0;
			if (num)
			{
				MelonLogger.Msg("[" + modId + "] Session settings cleared; local settings restored.");
				Changed();
			}
		}
	}
	internal interface ISetting
	{
		string Key { get; }

		object LocalValue { get; }

		bool IsValid(object value);

		object Parse(string value);
	}
	internal sealed class HostSetting<T> : ISetting
	{
		private readonly HostSettings owner;

		private readonly MelonPreferences_Entry<T> entry;

		private readonly T vanillaValue;

		private readonly Func<T, bool> validate;

		internal T Value => owner.GetValue(Key, entry.Value, vanillaValue);

		public string Key => ((MelonPreferences_Entry)entry).Identifier;

		public object LocalValue => entry.Value;

		internal HostSetting(HostSettings owner, MelonPreferences_Entry<T> entry, T vanillaValue, Func<T, bool> validate)
		{
			this.owner = owner;
			this.entry = entry;
			this.vanillaValue = vanillaValue;
			this.validate = validate;
		}

		public bool IsValid(object value)
		{
			if (!(value is T arg))
			{
				return false;
			}
			if (value is float f && (float.IsNaN(f) || float.IsInfinity(f)))
			{
				return false;
			}
			if (validate != null)
			{
				return validate(arg);
			}
			return true;
		}

		public object Parse(string value)
		{
			object obj;
			int result2;
			if (typeof(T) == typeof(bool) && bool.TryParse(value, out var result))
			{
				obj = result;
			}
			else if (typeof(T) == typeof(int) && int.TryParse(value, NumberStyles.Integer, CultureInfo.InvariantCulture, out result2))
			{
				obj = result2;
			}
			else
			{
				if (!(typeof(T) == typeof(float)) || !float.TryParse(value, NumberStyles.Float, CultureInfo.InvariantCulture, out var result3))
				{
					throw new FormatException("Invalid settings value: " + Key);
				}
				obj = result3;
			}
			if (!IsValid(obj))
			{
				throw new FormatException("Settings value is out of range: " + Key);
			}
			return obj;
		}
	}
	internal static class ModVersion
	{
		internal const string Current = "1.1.4";
	}
}
namespace FakePlayers
{
	public sealed class Core : MelonMod
	{
		public override void OnInitializeMelon()
		{
			FakePlayersPreferences.Initialize();
			FakePlayersPreferences.EffectiveValuesChanged += VWorldPatches.OnSettingsChanged;
			bool enabled = FakePlayersPreferences.Enabled;
			int fakePlayerCount = FakePlayersPreferences.FakePlayerCount;
			if (enabled)
			{
				MelonLogger.Msg($"FakePlayers initialized. Fake player count: {fakePlayerCount}");
			}
			else
			{
				MelonLogger.Msg("FakePlayers initialized but disabled.");
			}
		}

		public override void OnUpdate()
		{
			HostSettings.Update();
			FakePlayerManager.Update();
		}
	}
}
namespace FakePlayers.Patches
{
	[HarmonyPatch(typeof(GameMainBase), "ResolveNickName", new Type[]
	{
		typeof(string),
		typeof(string)
	})]
	internal static class PlayerNamePatch
	{
		private static bool Prefix(string steamID, ref string __result)
		{
			if (!ulong.TryParse(steamID, out var result) || !FakePlayerManager.TryGetName(result, out var name))
			{
				return true;
			}
			__result = name;
			return false;
		}
	}
	internal static class VWorldPatches
	{
		[HarmonyPatch(typeof(VWorld), "OnDestroy")]
		internal static class DestroyPatch
		{
			[HarmonyPrefix]
			private static void Prefix(VWorld __instance)
			{
				if (_createdWorld == __instance && !CleanupCreatedPlayers())
				{
					MelonLogger.Warning("Fake session cleanup incomplete before VWorld.OnDestroy; continuing vanilla world teardown.");
				}
			}
		}

		[HarmonyPatch(typeof(VWorld), "RegistPlayer")]
		internal static class RegistPlayerPatch
		{
			private static void Postfix(VWorld __instance, ulong steamID, bool isHost, MsgErrorCode __result)
			{
				//IL_0007: Unknown result type (might be due to invalid IL or missing references)
				if (!FakePlayersPreferences.Enabled || (int)__result != 0 || !isHost || _createdWorld == __instance)
				{
					return;
				}
				try
				{
					VRoomManager vRoomManager = __instance.VRoomManager;
					if (vRoomManager == null)
					{
						MelonLogger.Warning("VRoomManager is null, cannot add fake players");
						return;
					}
					FieldInfo field = typeof(VRoomManager).GetField("_gameSessionInfo", BindingFlags.Instance | BindingFlags.NonPublic);
					if (field == null)
					{
						MelonLogger.Error("Could not find _gameSessionInfo field in VRoomManager");
						return;
					}
					object? value = field.GetValue(vRoomManager);
					GameSessionInfo val = (GameSessionInfo)((value is GameSessionInfo) ? value : null);
					if (val == null)
					{
						MelonLogger.Error("_gameSessionInfo is null in VRoomManager");
						return;
					}
					if (!CleanupCreatedPlayers())
					{
						MelonLogger.Error("Cannot create fake players in the new world: previous fake session cleanup is incomplete.");
						return;
					}
					_createdWorld = __instance;
					_createdSessionInfo = val;
					FakePlayerManager.CreateFakePlayers(__instance, val);
				}
				catch (Exception ex)
				{
					MelonLogger.Error("Error in RegistPlayerPatch: " + ex.Message + "\n" + ex.StackTrace);
				}
			}
		}

		private static VWorld _createdWorld;

		private static GameSessionInfo _createdSessionInfo;

		internal static void OnSettingsChanged()
		{
			if (!FakePlayersPreferences.IsHostSession && !CleanupCreatedPlayers())
			{
				MelonLogger.Warning("Fake session cleanup remains incomplete after settings reset; new fake creation is blocked.");
			}
		}

		private static bool CleanupCreatedPlayers()
		{
			if (_createdWorld == null)
			{
				return true;
			}
			if (!FakePlayerManager.RemoveFakePlayers(_createdSessionInfo))
			{
				return false;
			}
			_createdWorld = null;
			_createdSessionInfo = null;
			return true;
		}
	}
}
namespace FakePlayers.Managers
{
	internal static class FakePlayerManager
	{
		private static readonly List<FakeSession> _fakeSessions = new List<FakeSession>();

		private static readonly FieldInfo SessionManagerField = typeof(VWorld).GetField("_sessionManager", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo DispatchersField = typeof(VWorld).GetField("_serverDispatchers", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo VersionField = typeof(VWorld).GetField("Version", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo PlayerField = typeof(SessionContext).GetField("_vPlayer", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo InviteField = typeof(Hub).GetField("steamInviteDispatcher", BindingFlags.Instance | BindingFlags.NonPublic);

		private static readonly FieldInfo HostContextField = typeof(SessionManager).GetField("_hostSessionContext", BindingFlags.Instance | BindingFlags.NonPublic);

		private static VWorld _world;

		private static SessionManager _sessionManager;

		private static ServerDispatchManager _dispatchers;

		private static int _remaining;

		private static int _created;

		private static bool _removing;

		internal static bool TryGetName(ulong steamID, out string name)
		{
			foreach (FakeSession fakeSession in _fakeSessions)
			{
				IContext context = fakeSession.Context;
				SessionContext val = (SessionContext)(object)((context is SessionContext) ? context : null);
				if (val != null && val.SteamID == steamID && steamID != 0L)
				{
					name = fakeSession.Name;
					return true;
				}
			}
			name = null;
			return false;
		}

		internal static void CreateFakePlayers(VWorld world, GameSessionInfo gameSessionInfo)
		{
			//IL_0025: Unknown result type (might be due to invalid IL or missing references)
			//IL_002f: Expected O, but got Unknown
			//IL_004e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0058: Expected O, but got Unknown
			_world = world;
			_sessionManager = (SessionManager)(SessionManagerField ?? throw new MissingFieldException("VWorld", "_sessionManager")).GetValue(world);
			_dispatchers = (ServerDispatchManager)(DispatchersField ?? throw new MissingFieldException("VWorld", "_serverDispatchers")).GetValue(world);
			_remaining = (FakePlayersPreferences.Enabled ? FakePlayersPreferences.FakePlayerCount : 0);
			_created = 0;
			if (_remaining > 0)
			{
				MelonLogger.Msg($"Waiting for the host avatar before joining {_remaining} fake players.");
			}
		}

		private static VPlayer GetHostPlayer()
		{
			//IL_0023: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Expected O, but got Unknown
			//IL_004b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0051: Expected O, but got Unknown
			SessionContext val = (SessionContext)(HostContextField ?? throw new MissingFieldException("SessionManager", "_hostSessionContext")).GetValue(_sessionManager);
			if (val != null)
			{
				return (VPlayer)(PlayerField ?? throw new MissingFieldException("SessionContext", "_vPlayer")).GetValue(val);
			}
			return null;
		}

		internal static void Update()
		{
			//IL_0089: Unknown result type (might be due to invalid IL or missing references)
			//IL_008f: Expected O, but got Unknown
			//IL_009b: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a0: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ba: Unknown result type (might be due to invalid IL or missing references)
			//IL_00cf: Unknown result type (might be due to invalid IL or missing references)
			//IL_00db: Unknown result type (might be due to invalid IL or missing references)
			//IL_0109: Unknown result type (might be due to invalid IL or missing references)
			//IL_012d: 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_0148: Expected O, but got Unknown
			if (_world == null)
			{
				return;
			}
			try
			{
				if (_removing)
				{
					RemoveFakePlayers(null);
					return;
				}
				VPlayer hostPlayer = GetHostPlayer();
				if (_remaining > 0 && hostPlayer != null && hostPlayer.LevelLoadCompleted)
				{
					int num = ++_created;
					_remaining--;
					FakeSession fakeSession = new FakeSession(_sessionManager.GetNewSessionID(), _dispatchers, $"FakePlayer{num}");
					_fakeSessions.Add(fakeSession);
					SessionContext val = new SessionContext((ISession)(object)fakeSession);
					_sessionManager.Add(val);
					fakeSession.Request((IMsg)new JoinServerReq
					{
						guid = Guid.NewGuid().ToString(),
						steamID = (ulong)(76561198000000000L + num - 1),
						nickName = fakeSession.Name,
						clientVersion = (string)(VersionField ?? throw new MissingFieldException("VWorld", "Version")).GetValue(_world),
						roomPassword = ((SteamInviteDispatcher)(InviteField ?? throw new MissingFieldException("Hub", "steamInviteDispatcher")).GetValue(Hub.s)).RoomPassword,
						isHost = false
					});
				}
				FakeSession[] array = _fakeSessions.ToArray();
				foreach (FakeSession fakeSession2 in array)
				{
					if (fakeSession2.Failed)
					{
						if (!fakeSession2.RemovalRequested)
						{
							_sessionManager.Remove((long)fakeSession2.ID, (DisconnectReason)3);
							fakeSession2.RemovalRequested = true;
							MelonLogger.Msg($"Rejected fake session removal requested: {fakeSession2.Name}, session {fakeSession2.ID}.");
						}
						if (_sessionManager.GetSession((long)fakeSession2.ID) == null)
						{
							_fakeSessions.Remove(fakeSession2);
							MelonLogger.Msg($"Rejected fake session removal confirmed: {fakeSession2.Name}, session {fakeSession2.ID}.");
						}
					}
					else
					{
						fakeSession2.Update(hostPlayer);
					}
				}
			}
			catch (Exception arg)
			{
				_remaining = 0;
				MelonLogger.Error($"Fake player update failed; further joins stopped: {arg}");
				_removing = true;
			}
		}

		internal static bool RemoveFakePlayers(GameSessionInfo gameSessionInfo)
		{
			_remaining = 0;
			_removing = true;
			FakeSession[] array = _fakeSessions.ToArray();
			foreach (FakeSession fakeSession in array)
			{
				try
				{
					if (!fakeSession.RemovalRequested)
					{
						fakeSession.RemovalRequested = true;
						fakeSession.Close((DisconnectReason)3);
						_sessionManager.Remove((long)fakeSession.ID, (DisconnectReason)3);
						MelonLogger.Msg($"Fake session removal requested: {fakeSession.Name}, session {fakeSession.ID}.");
					}
					if (_sessionManager.GetSession((long)fakeSession.ID) == null)
					{
						_fakeSessions.Remove(fakeSession);
					}
				}
				catch (Exception arg)
				{
					MelonLogger.Error($"Fake session removal failed: {fakeSession.Name}, session {fakeSession.ID}: {arg}");
				}
			}
			if (_fakeSessions.Count != 0)
			{
				return false;
			}
			_world = null;
			_sessionManager = null;
			_dispatchers = null;
			_removing = false;
			MelonLogger.Msg("Fake session removal confirmed.");
			return true;
		}
	}
	internal sealed class FakeSession : ISession
	{
		private readonly ServerDispatchManager _dispatchers;

		private readonly Queue<IMsg> _requests = new Queue<IMsg>();

		private bool _running = true;

		private bool _joined;

		private VRoomType? _nextRoom;

		private float _deadline;

		private float _nextHeartbeat;

		private string _pending;

		public int ID { get; }

		public IContext Context { get; private set; }

		internal string Name { get; }

		internal bool Failed { get; private set; }

		internal bool RemovalRequested { get; set; }

		internal int HeartbeatAcknowledgements { get; private set; }

		internal FakeSession(int id, ServerDispatchManager dispatchers, string name)
		{
			ID = id;
			_dispatchers = dispatchers;
			Name = name;
		}

		internal void Request(IMsg message)
		{
			if (Version.IsEnable)
			{
				message.hashCode = Version.GetProtocolHashCode();
			}
			_requests.Enqueue(message);
			if (!(message is HeartBeatReq))
			{
				_pending = ((object)message).GetType().Name;
				_deadline = Time.realtimeSinceStartup + 30f;
				MelonLogger.Msg($"Fake request: {Name}, {_pending}, session {ID}.");
			}
		}

		internal void Update(VPlayer host)
		{
			//IL_006d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0073: Expected O, but got Unknown
			//IL_004c: Unknown result type (might be due to invalid IL or missing references)
			//IL_0056: Expected O, but got Unknown
			//IL_00aa: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00c5: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ca: 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_00cd: Unknown result type (might be due to invalid IL or missing references)
			//IL_00e7: Expected I4, but got Unknown
			//IL_0103: Unknown result type (might be due to invalid IL or missing references)
			//IL_010d: Expected O, but got Unknown
			//IL_0110: Unknown result type (might be due to invalid IL or missing references)
			//IL_0115: Unknown result type (might be due to invalid IL or missing references)
			//IL_012b: Expected O, but got Unknown
			//IL_00ea: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ef: Unknown result type (might be due to invalid IL or missing references)
			//IL_0100: Expected O, but got Unknown
			//IL_012e: Unknown result type (might be due to invalid IL or missing references)
			//IL_0138: Expected O, but got Unknown
			//IL_013b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0145: Expected O, but got Unknown
			//IL_0153: Unknown result type (might be due to invalid IL or missing references)
			if (!_running)
			{
				return;
			}
			if (_pending != null && Time.realtimeSinceStartup >= _deadline)
			{
				Fail(_pending + " timed out after 30 seconds");
				return;
			}
			if (_joined && Time.realtimeSinceStartup >= _nextHeartbeat)
			{
				Request((IMsg)new HeartBeatReq());
				_nextHeartbeat = Time.realtimeSinceStartup + 10f;
			}
			SessionContext val = (SessionContext)Context;
			if (_nextRoom.HasValue && !val.ExistPlayer() && host != null && host.LevelLoadCompleted && ((VActor)host).VRoom.Property.vRoomType == _nextRoom.Value)
			{
				VRoomType value = _nextRoom.Value;
				switch (value - 1)
				{
				case 2:
					Request((IMsg)new EnterMaintenanceRoomReq
					{
						playerUID = val.GetPlayerUID()
					});
					break;
				case 0:
					Request((IMsg)new EnterWaitingRoomReq());
					break;
				case 1:
					Request((IMsg)new EnterDungeonReq
					{
						roomID = ((VActor)host).VRoom.RoomID
					});
					break;
				case 3:
					Request((IMsg)new EnterDeathMatchRoomReq());
					break;
				case 4:
					Request((IMsg)new EnterLogisticsRoomReq());
					break;
				default:
					Fail($"Unsupported room type {_nextRoom.Value}");
					return;
				}
				_nextRoom = null;
			}
			while (_running && _requests.Count > 0)
			{
				_dispatchers.Dispatch((ISession)(object)this, _requests.Dequeue());
			}
		}

		private void Receive(IMsg message)
		{
			//IL_0046: Unknown result type (might be due to invalid IL or missing references)
			//IL_004c: Expected O, but got Unknown
			//IL_004d: Unknown result type (might be due to invalid IL or missing references)
			//IL_0066: Unknown result type (might be due to invalid IL or missing references)
			//IL_0076: Unknown result type (might be due to invalid IL or missing references)
			//IL_007d: Invalid comparison between Unknown and I4
			//IL_009c: Unknown result type (might be due to invalid IL or missing references)
			//IL_00a6: Expected O, but got Unknown
			//IL_014a: Unknown result type (might be due to invalid IL or missing references)
			//IL_00ab: Unknown result type (might be due to invalid IL or missing references)
			//IL_00b5: Expected O, but got Unknown
			//IL_01b7: Unknown result type (might be due to invalid IL or missing references)
			//IL_0158: Unknown result type (might be due to invalid IL or missing references)
			//IL_01e6: Unknown result type (might be due to invalid IL or missing references)
			//IL_01c6: Unknown result type (might be due to invalid IL or missing references)
			//IL_00f9: Unknown result type (might be due to invalid IL or missing references)
			//IL_0111: Unknown result type (might be due to invalid IL or missing references)
			bool flag = message is EnterMaintenanceRoomRes || message is EnterWaitingRoomRes || message is EnterDungeonRes || message is EnterDeathMatchRoomRes || message is EnterLogisticsRoomRes;
			if (message is JoinServerRes || flag || message is LevelLoadCompleteRes)
			{
				IResMsg val = (IResMsg)message;
				if ((int)val.errorCode != 0)
				{
					Fail($"{((object)message).GetType().Name}: {val.errorCode}", (int)val.errorCode == 18);
					return;
				}
				_pending = null;
				if (message is JoinServerRes)
				{
					_joined = true;
					Request((IMsg)new PlayModeInfoReq());
					return;
				}
				if (flag)
				{
					Request((IMsg)new LevelLoadCompleteReq());
					return;
				}
				LevelLoadCompleteRes val2 = (LevelLoadCompleteRes)(object)((message is LevelLoadCompleteRes) ? message : null);
				if (val2 != null)
				{
					if (val2.selfInfo.isHost)
					{
						Fail("Fake actor was classified as host");
						return;
					}
					MelonLogger.Msg($"Fake avatar entered: {Name}, SteamID {((SessionContext)Context).SteamID}, UID {((SessionContext)Context).GetPlayerUID()}, actor {((ActorBaseInfo)val2.selfInfo).actorID}.");
				}
				return;
			}
			HeartBeatRes val3 = (HeartBeatRes)(object)((message is HeartBeatRes) ? message : null);
			if (val3 != null)
			{
				if ((int)((IResMsg)val3).errorCode != 0)
				{
					Fail($"HeartBeatRes: {((IResMsg)val3).errorCode}");
				}
				else if (++HeartbeatAcknowledgements == 1)
				{
					MelonLogger.Msg($"Fake heartbeat confirmed: {Name}, session {ID}.");
				}
				return;
			}
			PlayModeInfoRes val4 = (PlayModeInfoRes)(object)((message is PlayModeInfoRes) ? message : null);
			if (val4 != null)
			{
				if ((int)((IResMsg)val4).errorCode != 0)
				{
					Fail($"PlayModeInfoRes: {((IResMsg)val4).errorCode}");
					return;
				}
				_pending = null;
				_nextRoom = val4.currentRoomType;
			}
			else if (message is MoveToMaintenanceRoomSig)
			{
				_nextRoom = (VRoomType)3;
			}
			else if (message is MoveToWaitingRoomSig)
			{
				_nextRoom = (VRoomType)1;
			}
			else if (message is MoveToDungeonSig)
			{
				_nextRoom = (VRoomType)2;
			}
			else if (message is MoveToDeathMatchSig)
			{
				_nextRoom = (VRoomType)4;
			}
			else if (message is MoveToLogisticsSig)
			{
				_nextRoom = (VRoomType)5;
			}
		}

		private void Fail(string cause, bool full = false)
		{
			if (!Failed)
			{
				if (full)
				{
					MelonLogger.Warning($"Fake join rejected by player limit: {Name}, session {ID}: {cause}");
				}
				else
				{
					MelonLogger.Error($"Fake session failed: {Name}, session {ID}: {cause}");
				}
				Failed = true;
				Close((DisconnectReason)3);
			}
		}

		public SendResult SendLink(params IMsg[] messages)
		{
			if (_running)
			{
				foreach (IMsg message in messages)
				{
					Receive(message);
				}
				return (SendResult)0;
			}
			return (SendResult)2;
		}

		public SendResult SendError<TResponse>(MsgErrorCode errorCode, string file = "", int line = 0) where TResponse : IResMsg, new()
		{
			//IL_0014: 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)
			IMsg[] array = new IMsg[1];
			TResponse val = new TResponse();
			((IResMsg)val).errorCode = errorCode;
			array[0] = (IMsg)(object)val;
			return SendLink((IMsg[])(object)array);
		}

		public bool SendLoopback(IMsg message)
		{
			if (_running)
			{
				return _dispatchers.LoopbackLink((ISession)(object)this, message);
			}
			return false;
		}

		public void Connect(IConnection connection)
		{
			throw new InvalidOperationException("FakeSession uses an in-process transport.");
		}

		public void SetContext(IContext context)
		{
			Context = context;
		}

		public void BeginClose(DisconnectReason reason)
		{
			//IL_0001: Unknown result type (might be due to invalid IL or missing references)
			Close(reason);
		}

		public void Close(DisconnectReason reason)
		{
			_running = false;
			_requests.Clear();
		}

		public bool IsRunning()
		{
			return _running;
		}

		public IPEndPoint GetRemoteEndPoint()
		{
			return null;
		}
	}
}
namespace FakePlayers.Config
{
	internal static class FakePlayersPreferences
	{
		private const string CategoryId = "FakePlayers";

		private static MelonPreferences_Category _category;

		private static MelonPreferences_Entry<int> _fakePlayerCount;

		private static MelonPreferences_Entry<bool> _enabled;

		private static HostSetting<bool> _effectiveEnabled;

		private static HostSetting<int> _effectiveFakePlayerCount;

		private static HostSettings _hostSettings;

		internal static bool IsHostSession
		{
			get
			{
				if (_hostSettings != null && _hostSettings.IsInSession)
				{
					return _hostSettings.IsHost;
				}
				return false;
			}
		}

		internal static bool Enabled => _effectiveEnabled?.Value ?? _enabled?.Value ?? true;

		internal static int FakePlayerCount => _effectiveFakePlayerCount?.Value ?? _fakePlayerCount?.Value ?? 3;

		internal static event Action EffectiveValuesChanged;

		internal static void Initialize()
		{
			if (_category == null)
			{
				_category = MelonPreferences.CreateCategory("FakePlayers", "FakePlayers");
				_enabled = CreateEntry("Enabled", defaultValue: true, "Enable Fake Players", "Enable or disable fake player emulation for testing.");
				_fakePlayerCount = CreateEntry("FakePlayerCount", 3, "Fake Player Count", "The number of fake players to spawn. Default: 3 (for a total of 4 players including yourself). Minimum: 0, Maximum: 32762.");
				if (_fakePlayerCount.Value < 0)
				{
					MelonLogger.Warning($"FakePlayerCount value {_fakePlayerCount.Value} is below minimum of 0. Clamping to 0.");
					_fakePlayerCount.Value = 0;
				}
				else if (_fakePlayerCount.Value > 32762)
				{
					MelonLogger.Warning($"FakePlayerCount value {_fakePlayerCount.Value} exceeds maximum of (32766 - 4). Clamping to (32766 - 4).");
					_fakePlayerCount.Value = 32762;
				}
				_hostSettings = new HostSettings("FakePlayers");
				_effectiveEnabled = _hostSettings.Register(_enabled, vanillaValue: false);
				_effectiveFakePlayerCount = _hostSettings.Register(_fakePlayerCount, 0, (int value) => value >= 0 && value <= 32762);
				_hostSettings.EffectiveValuesChanged += delegate
				{
					FakePlayersPreferences.EffectiveValuesChanged?.Invoke();
				};
			}
		}

		private static MelonPreferences_Entry<T> CreateEntry<T>(string identifier, T defaultValue, string displayName, string description = null)
		{
			if (_category == null)
			{
				throw new InvalidOperationException("Preference category not initialized.");
			}
			return _category.CreateEntry<T>(identifier, defaultValue, displayName, description, false, false, (ValueValidator)null, (string)null);
		}
	}
}