The BepInEx console will not appear when launching like it does for other games on Thunderstore. This is normal (and helps prevent crashes during startup). You can turn it back on in your BepInEx.cfg file.
Decompiled source of LocalMultiXtras v1.1.0
LocalMultiXtras.dll
Decompiled a week agousing 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.Text; using System.Threading; using BepInEx; using BepInEx.Configuration; using HarmonyLib; using MasterSlaveInput; using Microsoft.CodeAnalysis; using Photon.Pun; using Photon.Realtime; using Steamworks; using TMPro; using UnityEngine; using UnityEngine.TextCore; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("LocalMultiXtras")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("LocalMultiXtras")] [assembly: AssemblyTitle("LocalMultiXtras")] [assembly: AssemblyVersion("1.0.0.0")] namespace Microsoft.CodeAnalysis { [CompilerGenerated] [Microsoft.CodeAnalysis.Embedded] internal sealed class EmbeddedAttribute : Attribute { } } namespace System.Runtime.CompilerServices { [CompilerGenerated] [Microsoft.CodeAnalysis.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] [Microsoft.CodeAnalysis.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; } } } public class UIManager : MonoBehaviour { [CompilerGenerated] private sealed class <FindAndApplyGameAssets>d__4 : IEnumerator<object>, IEnumerator, IDisposable { private int <>1__state; private object <>2__current; public UIManager <>4__this; private TMP_FontAsset <gameFont>5__1; private Material <outlineMaterial>5__2; object? IEnumerator<object>.Current { [DebuggerHidden] get { return <>2__current; } } object? IEnumerator.Current { [DebuggerHidden] get { return <>2__current; } } [DebuggerHidden] public <FindAndApplyGameAssets>d__4(int <>1__state) { this.<>1__state = <>1__state; } [DebuggerHidden] void IDisposable.Dispose() { <gameFont>5__1 = null; <outlineMaterial>5__2 = null; <>1__state = -2; } private bool MoveNext() { //IL_0026: Unknown result type (might be due to invalid IL or missing references) //IL_0030: Expected O, but got Unknown //IL_013c: Unknown result type (might be due to invalid IL or missing references) //IL_0141: Unknown result type (might be due to invalid IL or missing references) switch (<>1__state) { default: return false; case 0: <>1__state = -1; <>2__current = (object)new WaitForSeconds(1f); <>1__state = 1; return true; case 1: <>1__state = -1; <gameFont>5__1 = ((IEnumerable<TMP_FontAsset>)Resources.FindObjectsOfTypeAll<TMP_FontAsset>()).FirstOrDefault((Func<TMP_FontAsset, bool>)delegate(TMP_FontAsset a) { //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) FaceInfo faceInfo = ((TMP_Asset)a).faceInfo; return ((FaceInfo)(ref faceInfo)).familyName == "Daruma Drop One"; }); if ((Object)(object)<gameFont>5__1 != (Object)null && (Object)(object)<>4__this._uiText != (Object)null) { ((TMP_Text)<>4__this._uiText).font = <gameFont>5__1; } <outlineMaterial>5__2 = ((IEnumerable<Material>)Resources.FindObjectsOfTypeAll<Material>()).FirstOrDefault((Func<Material, bool>)((Material m) => ((Object)m).name.IndexOf("Daruma", StringComparison.OrdinalIgnoreCase) >= 0 && ((Object)m).name.IndexOf("Outline", StringComparison.OrdinalIgnoreCase) >= 0)); if ((Object)(object)<outlineMaterial>5__2 != (Object)null && (Object)(object)<>4__this._uiText != (Object)null) { ((TMP_Text)<>4__this._uiText).fontMaterial = <outlineMaterial>5__2; ((TMP_Text)<>4__this._uiText).outlineWidth = 0.1f; ((TMP_Text)<>4__this._uiText).outlineColor = Color32.op_Implicit(Color.black); } return false; } } bool IEnumerator.MoveNext() { //ILSpy generated this explicit interface implementation from .override directive in MoveNext return this.MoveNext(); } [DebuggerHidden] void IEnumerator.Reset() { throw new NotSupportedException(); } } private TextMeshProUGUI? _uiText; private Canvas? _canvas; private void Awake() { if (!ConfigManager.EnableUI.Value) { Object.Destroy((Object)(object)((Component)this).gameObject); } } private void Start() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Expected O, but got Unknown //IL_003f: Unknown result type (might be due to invalid IL or missing references) //IL_0045: Expected O, but got Unknown //IL_00a7: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) //IL_00d3: Unknown result type (might be due to invalid IL or missing references) //IL_00e9: Unknown result type (might be due to invalid IL or missing references) //IL_00ff: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("MSI_Canvas"); _canvas = val.AddComponent<Canvas>(); _canvas.renderMode = (RenderMode)0; _canvas.sortingOrder = 100; Object.DontDestroyOnLoad((Object)(object)val); GameObject val2 = new GameObject("MSI_Text"); val2.transform.SetParent(((Component)_canvas).transform); _uiText = val2.AddComponent<TextMeshProUGUI>(); ((TMP_Text)_uiText).fontSize = ConfigManager.FontSize.Value; ((TMP_Text)_uiText).alignment = (TextAlignmentOptions)257; RectTransform rectTransform = ((TMP_Text)_uiText).rectTransform; rectTransform.anchorMin = new Vector2(0f, 1f); rectTransform.anchorMax = new Vector2(0f, 1f); rectTransform.pivot = new Vector2(0f, 1f); rectTransform.anchoredPosition = new Vector2(15f, -15f); rectTransform.sizeDelta = new Vector2(600f, 400f); ((MonoBehaviour)this).StartCoroutine(FindAndApplyGameAssets()); } [IteratorStateMachine(typeof(<FindAndApplyGameAssets>d__4))] private IEnumerator FindAndApplyGameAssets() { //yield-return decompiler failed: Unexpected instruction in Iterator.Dispose() return new <FindAndApplyGameAssets>d__4(0) { <>4__this = this }; } private void Update() { //IL_002c: 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_00f8: Unknown result type (might be due to invalid IL or missing references) if ((Object)(object)_canvas == (Object)null || (Object)(object)_uiText == (Object)null) { return; } if (Input.GetKeyDown(ConfigManager.ToggleUIKey.Value)) { ((Component)_canvas).gameObject.SetActive(!((Component)_canvas).gameObject.activeSelf); } if (!((Component)_canvas).gameObject.activeSelf) { return; } if (!PhotonNetwork.InRoom) { ((TMP_Text)_uiText).text = ""; return; } StringBuilder stringBuilder = new StringBuilder(); if (PhotonNetwork.IsMasterClient) { string arg = (InputBroadcaster.IsBroadcasting ? "<color=green>ACTIVE</color>" : "<color=red>PAUSED</color>"); stringBuilder.AppendLine($"[MASTER] Broadcasting: {arg} ({ConfigManager.BroadcastToggleKey.Value})"); stringBuilder.AppendLine($"Toggle Control: {ConfigManager.ModifierKey.Value}+[2-9]"); stringBuilder.AppendLine("--------------------"); Player[] playerList = PhotonNetwork.PlayerList; foreach (Player val in playerList) { string arg2 = (val.IsMasterClient ? "<color=white>[HOST]</color>" : ((InputBroadcaster.SlavePausedStates.ContainsKey(val.ActorNumber) && InputBroadcaster.SlavePausedStates[val.ActorNumber]) ? "<color=red>[PAUSED]</color>" : "<color=green>[ACTIVE]</color>")); stringBuilder.AppendLine($" {val.ActorNumber}: {val.NickName} {arg2}"); } } else { string text = (InputPatches.IsSlaveControlPaused ? "<color=red>PAUSED</color>" : "<color=green>ACTIVE</color>"); stringBuilder.AppendLine("[INSTANCE] Master Control: " + text); } ((TMP_Text)_uiText).text = stringBuilder.ToString(); } } namespace MasterSlaveInput { public static class ConfigManager { public static ConfigFile Config { get; private set; } public static ConfigEntry<KeyCode> ModifierKey { get; private set; } public static ConfigEntry<KeyCode> MasterPauseKey { get; private set; } public static ConfigEntry<bool> AutoJoinOnLaunch { get; private set; } public static ConfigEntry<bool> EnableUI { get; private set; } public static ConfigEntry<KeyCode> ToggleUIKey { get; private set; } public static ConfigEntry<int> FontSize { get; private set; } public static ConfigEntry<bool> AutoRenameInstances { get; private set; } public static ConfigEntry<string> SyncedInputs { get; private set; } public static ConfigEntry<KeyCode> BroadcastToggleKey { get; private set; } public static void Initialize(ConfigFile config) { Config = config; ModifierKey = Config.Bind<KeyCode>("1. Key Binds", "ModifierKey", (KeyCode)304, "The key to hold to use number keys to toggle instance control."); ToggleUIKey = Config.Bind<KeyCode>("1. Key Binds", "ToggleUIKey", (KeyCode)289, "The key to press to show or hide the UI in-game."); MasterPauseKey = Config.Bind<KeyCode>("1. Key Binds", "MasterPauseKey", (KeyCode)290, "On the master instance, this key pauses control of the character while still broadcasting input to instances."); BroadcastToggleKey = Config.Bind<KeyCode>("1. Key Binds", "BroadcastToggleKey", (KeyCode)291, "On the master instance, this key toggles input broadcasting on and off for all instances."); EnableUI = Config.Bind<bool>("2. UI", "EnableUI", true, "Enable the master/instances status UI."); FontSize = Config.Bind<int>("2. UI", "FontSize", 12, "The font size of the UI text."); AutoRenameInstances = Config.Bind<bool>("3. Instances", "AutoRenameInstances", true, "If enabled, all instances will be automatically renamed to 'Instance #X'."); AutoJoinOnLaunch = Config.Bind<bool>("3. Instances", "AutoJoin", true, "If enabled, all instances will automatically try to join the host's lobby."); SyncedInputs = Config.Bind<string>("4. Syncing", "SyncedInputs", "movementInput, lookInput, jumpWasPressed, sprintIsPressed, interactWasPressed, interactIsPressed, dropWasPressed, usePrimaryWasPressed, usePrimaryIsPressed, useSecondaryWasPressed, useSecondaryIsPressed, crouchIsPressed, crouchWasPressed, emoteIsPressed", "A comma-separated list of the field names from the CharacterInput class to synchronize. Add or remove fields to customize what is controlled.\nAcceptable values: movementInput, sprintWasPressed, sprintIsPressed, sprintToggleIsPressed, sprintToggleWasPressed, jumpWasPressed, jumpIsPressed, dropWasPressed, dropIsPressed, dropWasReleased, scrollInput, lookInput, usePrimaryWasPressed, usePrimaryIsPressed, usePrimaryWasReleased, useSecondaryWasPressed, useSecondaryIsPressed, useSecondaryWasReleased, crouchWasPressed, crouchIsPressed, crouchToggleWasPressed, scrolledUp, scrolledDown, pauseWasPressed, interactWasPressed, interactIsPressed, interactWasReleased, emoteIsPressed, spectateLeftWasPressed, spectateRightWasPressed, selectBackpackWasPressed, scrollButtonLeftWasPressed, scrollButtonRightWasPressed, pingWasPressed, pushToTalkPressed, unselectSlotWasPressed, selectSlotForwardWasPressed, selectSlotBackwardWasPressed, unselectSlotWasPressed"); } } public class InputBroadcaster : MonoBehaviourPunCallbacks { public static readonly Dictionary<string, object> SyncedInputData = new Dictionary<string, object>(); private static readonly List<FieldInfo> _fieldsToSync = new List<FieldInfo>(); private CharacterInput? m_characterInput; private bool _isInitialized = false; public static bool IsBroadcasting = true; public static bool IsMasterPaused = false; public static readonly Dictionary<int, bool> SlavePausedStates = new Dictionary<int, bool>(); public static void InitializeSyncFields() { string[] array = ConfigManager.SyncedInputs.Value.Split(','); string[] array2 = array; foreach (string text in array2) { string text2 = text.Trim(); if (!string.IsNullOrEmpty(text2)) { FieldInfo field = typeof(CharacterInput).GetField(text2, BindingFlags.Instance | BindingFlags.Public); if (field != null) { _fieldsToSync.Add(field); } else { Debug.LogWarning((object)("[LocalMultiXtras] Configured input field '" + text2 + "' not found and will be ignored.")); } } } Debug.Log((object)$"[LocalMultiXtras] Initialized with {_fieldsToSync.Count} fields to synchronize."); } [PunRPC] public void SetControlPaused(bool isPaused) { InputPatches.IsSlaveControlPaused = isPaused; } private void Update() { //IL_00ee: Unknown result type (might be due to invalid IL or missing references) //IL_0110: Unknown result type (might be due to invalid IL or missing references) if (!_isInitialized) { if (((MonoBehaviourPun)this).photonView.IsMine && (Object)(object)Player.localPlayer != (Object)null && (Object)(object)Player.localPlayer.character != (Object)null) { m_characterInput = ((Component)Player.localPlayer.character).GetComponent<CharacterInput>(); if ((Object)(object)m_characterInput != (Object)null) { _isInitialized = true; if (!PhotonNetwork.IsMasterClient && ConfigManager.AutoRenameInstances.Value) { PhotonNetwork.NickName = $"Instance {PhotonNetwork.LocalPlayer.ActorNumber}"; } } } else if (!((MonoBehaviourPun)this).photonView.IsMine) { _isInitialized = true; } } else if (PhotonNetwork.IsMasterClient && ((MonoBehaviourPun)this).photonView.IsMine) { if (Input.GetKeyDown(ConfigManager.MasterPauseKey.Value)) { IsMasterPaused = !IsMasterPaused; } if (Input.GetKeyDown(ConfigManager.BroadcastToggleKey.Value)) { IsBroadcasting = !IsBroadcasting; } HandleMasterToggles(); } } private void HandleMasterToggles() { //IL_0006: Unknown result type (might be due to invalid IL or missing references) if (!Input.GetKey(ConfigManager.ModifierKey.Value)) { return; } if (Input.GetKeyDown((KeyCode)48)) { SlavePausedStates.Clear(); ((MonoBehaviourPun)this).photonView.RPC("SetControlPaused", (RpcTarget)1, new object[1] { false }); return; } for (int i = 1; i < 10; i++) { if (Input.GetKeyDown((KeyCode)(48 + i))) { int num = i - 1; if (num >= PhotonNetwork.PlayerList.Length) { break; } Player val = PhotonNetwork.PlayerList[num]; if (!val.IsMasterClient) { int actorNumber = val.ActorNumber; bool flag = SlavePausedStates.ContainsKey(actorNumber) && SlavePausedStates[actorNumber]; bool flag2 = !flag; SlavePausedStates[actorNumber] = flag2; ((MonoBehaviourPun)this).photonView.RPC("SetControlPaused", val, new object[1] { flag2 }); break; } } } } [PunRPC] public void ReceiveInput(byte[] inputData) { if (!PhotonNetwork.IsMasterClient) { DeserializeAndStoreInputs(inputData); } } public void BroadcastInputs(CharacterInput source) { if (IsBroadcasting) { byte[] array = SerializeInputs(source); ((MonoBehaviourPun)this).photonView.RPC("ReceiveInput", (RpcTarget)1, new object[1] { array }); } } private byte[] SerializeInputs(CharacterInput input) { //IL_00a7: 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_00af: Unknown result type (might be due to invalid IL or missing references) //IL_00bd: Unknown result type (might be due to invalid IL or missing references) using MemoryStream memoryStream = new MemoryStream(); using (BinaryWriter binaryWriter = new BinaryWriter(memoryStream)) { foreach (FieldInfo item in _fieldsToSync) { object value = item.GetValue(input); if (item.FieldType == typeof(bool)) { binaryWriter.Write((bool)value); } else if (item.FieldType == typeof(float)) { binaryWriter.Write((float)value); } else if (item.FieldType == typeof(Vector2)) { Vector2 val = (Vector2)value; binaryWriter.Write(val.x); binaryWriter.Write(val.y); } } } return memoryStream.ToArray(); } public static void DeserializeAndStoreInputs(byte[] inputData) { //IL_00bf: Unknown result type (might be due to invalid IL or missing references) if (inputData == null || inputData.Length == 0) { return; } using MemoryStream input = new MemoryStream(inputData); using BinaryReader binaryReader = new BinaryReader(input); try { foreach (FieldInfo item in _fieldsToSync) { object value; if (item.FieldType == typeof(bool)) { value = binaryReader.ReadBoolean(); } else if (item.FieldType == typeof(float)) { value = binaryReader.ReadSingle(); } else { if (!(item.FieldType == typeof(Vector2))) { continue; } value = (object)new Vector2(binaryReader.ReadSingle(), binaryReader.ReadSingle()); } SyncedInputData[item.Name] = value; } } catch (EndOfStreamException) { } } } [HarmonyPatch] internal static class InputPatches { private static MethodInfo? _resetInputMethod; public static bool IsSlaveControlPaused; private static MethodInfo? _assignSpoofAccountMethod; private static bool _hasAttemptedAutoJoin; [HarmonyPatch(typeof(Player), "Awake")] [HarmonyPostfix] private static void AddInputBroadcaster(Player __instance) { if ((Object)(object)((Component)__instance).gameObject.GetComponent<InputBroadcaster>() == (Object)null) { ((Component)__instance).gameObject.AddComponent<InputBroadcaster>(); } } [HarmonyPatch(typeof(CharacterInput), "Sample")] [HarmonyPrefix] private static bool Prefix_HandleSlaveInput(CharacterInput __instance) { if (!PhotonNetwork.IsMasterClient && !IsSlaveControlPaused && (Object)(object)Player.localPlayer != (Object)null && (Object)(object)Player.localPlayer.character != (Object)null && (Object)(object)((Component)Player.localPlayer.character).GetComponent<CharacterInput>() == (Object)(object)__instance) { if (_resetInputMethod == null) { _resetInputMethod = typeof(CharacterInput).GetMethod("ResetInput", BindingFlags.Instance | BindingFlags.NonPublic); } _resetInputMethod?.Invoke(__instance, null); foreach (KeyValuePair<string, object> syncedInputDatum in InputBroadcaster.SyncedInputData) { typeof(CharacterInput).GetField(syncedInputDatum.Key)?.SetValue(__instance, syncedInputDatum.Value); } return false; } return true; } [HarmonyPatch(typeof(CharacterInput), "Sample")] [HarmonyPostfix] private static void Postfix_HandleMasterLogic(CharacterInput __instance) { if (!PhotonNetwork.IsMasterClient || !((Object)(object)Player.localPlayer != (Object)null) || !((Object)(object)Player.localPlayer.character != (Object)null) || !((Object)(object)((Component)Player.localPlayer.character).GetComponent<CharacterInput>() == (Object)(object)__instance)) { return; } InputBroadcaster component = ((Component)Player.localPlayer).GetComponent<InputBroadcaster>(); if ((Object)(object)component == (Object)null) { return; } component.BroadcastInputs(__instance); if (InputBroadcaster.IsMasterPaused) { if (_resetInputMethod == null) { _resetInputMethod = typeof(CharacterInput).GetMethod("ResetInput", BindingFlags.Instance | BindingFlags.NonPublic); } _resetInputMethod?.Invoke(__instance, null); } } [HarmonyPatch(typeof(MainMenu), "Initialize")] [HarmonyPostfix] private static void ResetAutoJoinFlag() { _hasAttemptedAutoJoin = false; } [HarmonyPatch(typeof(MainMenu), "Update")] [HarmonyPostfix] private static void AutoJoinSlaveInstance() { //IL_0114: Unknown result type (might be due to invalid IL or missing references) if (_hasAttemptedAutoJoin || !ConfigManager.AutoJoinOnLaunch.Value || LocalMultiXtrasPlugin.IsFirstInstance) { return; } _hasAttemptedAutoJoin = true; try { Type type = Type.GetType("com.quackandcheese.LocalMultiplayer.Helpers.GlobalSaveHelper, com.quackandcheese.LocalMultiplayer"); if (type == null) { return; } PropertyInfo property = type.GetProperty("SteamLobbyId", BindingFlags.Static | BindingFlags.Public); if (property == null) { return; } object value = property.GetValue(null); if (value == null) { return; } PropertyInfo property2 = value.GetType().GetProperty("Value"); if (property2 == null) { return; } ulong num = (ulong)property2.GetValue(value); if (num != 0) { if (_assignSpoofAccountMethod == null) { _assignSpoofAccountMethod = Type.GetType("com.quackandcheese.LocalMultiplayer.SteamAccountManager, LocalMultiplayer")?.GetMethod("AssignSpoofAccount", BindingFlags.Static | BindingFlags.Public); } _assignSpoofAccountMethod?.Invoke(null, null); GameHandler.GetService<SteamLobbyHandler>().TryJoinLobby(new CSteamID(num)); } } catch (Exception arg) { Debug.LogError((object)$"[LocalMultiXtras] AutoJoin failed: {arg}"); } } } [BepInPlugin("LocalMultiXtras", "Local Multi Xtras", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] public class LocalMultiXtrasPlugin : BaseUnityPlugin { private static Mutex? _instanceMutex; public static bool IsFirstInstance { get; private set; } private void Awake() { //IL_003d: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Expected O, but got Unknown _instanceMutex = new Mutex(initiallyOwned: true, "MasterSlaveInput.InstanceLock", out var createdNew); IsFirstInstance = createdNew; ConfigManager.Initialize(((BaseUnityPlugin)this).Config); InputBroadcaster.InitializeSyncFields(); Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), (string)null); GameObject val = new GameObject("MasterSlaveUIManager"); val.AddComponent<UIManager>(); Object.DontDestroyOnLoad((Object)(object)val); ((BaseUnityPlugin)this).Logger.LogInfo((object)"Local Multi Xtras Mod Loaded!"); ((BaseUnityPlugin)this).Logger.LogInfo((object)(IsFirstInstance ? "This is the primary (HOST) instance." : "This is a secondary instance.")); } private void OnDestroy() { if (IsFirstInstance) { _instanceMutex?.ReleaseMutex(); } _instanceMutex?.Close(); } } }