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