Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate-beta"
Decompiled source of MultiplayerPlus v0.1.0
Mods/MultiplayerPlus.dll
Decompiled 3 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.IO; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using HarmonyLib; using Il2Cpp; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.Networking; using Il2CppScheduleOne.Persistence; using Il2CppScheduleOne.UI; using Il2CppScheduleOne.UI.MainMenu; using Il2CppScheduleOne.UI.Multiplayer; using Il2CppSteamworks; using Il2CppSystem; using Il2CppTMPro; using MelonLoader; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using Multiplayer_FullGame; using UnityEngine; using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)] [assembly: MelonInfo(typeof(Multiplayer), "Multiplayer+", "2.0", "MedicalMess", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: ComVisible(false)] [assembly: Guid("56da624d-ba10-4216-9f7b-81b914327540")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: AssemblyCompany("Multiplayer+FullGame")] [assembly: AssemblyConfiguration("Debug")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("Multiplayer+FullGame")] [assembly: AssemblyTitle("Multiplayer+FullGame")] [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; } } } namespace Multiplayer_FullGame { public static class ModConfig { public static string CustomLobbyPassword = null; public static float LobbyCapacity = 20f; } [HarmonyPatch(typeof(Lobby), "Awake")] public class Lobby_Awake_Patch { private static void Postfix(Lobby __instance) { __instance.Players = Il2CppStructArray<CSteamID>.op_Implicit((CSteamID[])(object)new CSteamID[(int)ModConfig.LobbyCapacity]); MelonLogger.Msg($"Replaced Players array with new size: {(int)ModConfig.LobbyCapacity}"); } } [HarmonyPatch(typeof(LobbyInterface), "Awake")] public static class LobbyInterface_Awake_PostfixPatch { private static void Postfix(LobbyInterface __instance) { LobbyInterface __instance2 = __instance; if ((Object)(object)__instance2.Lobby != (Object)null) { __instance2.Lobby.onLobbyChange = Action.op_Implicit((Action)delegate { Singleton<LobbyInterface>.Instance.UpdateButtons(); Singleton<LobbyInterface>.Instance.UpdatePlayers(); ((TMP_Text)__instance2.LobbyTitle).text = "Lobby (" + __instance2.Lobby.PlayerCount + "/" + ModConfig.LobbyCapacity + ")"; }); } } } [HarmonyPatch(typeof(LobbyInterface), "UpdateButtons")] public static class LobbyInterface_UpdateButtons_Patch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { List<CodeInstruction> codes = new List<CodeInstruction>(instructions); for (int i = 0; i < codes.Count; i++) { if (codes[i].opcode == OpCodes.Ldc_I4_4) { yield return new CodeInstruction(OpCodes.Ldsfld, (object)AccessTools.Field(typeof(ModConfig), "LobbyCapacity")); yield return new CodeInstruction(OpCodes.Conv_I4, (object)null); } else { yield return codes[i]; } } } } [HarmonyPatch(typeof(Lobby), "TryOpenInviteInterface")] public class Lobby_TryOpenInviteInterface_Patch { private static IEnumerable<CodeInstruction> Transpiler(IEnumerable<CodeInstruction> instructions) { int newPlayerLimit = (int)ModConfig.LobbyCapacity; foreach (CodeInstruction instruction in instructions) { if (instruction.opcode == OpCodes.Ldc_I4_4) { yield return new CodeInstruction(OpCodes.Ldc_I4, (object)newPlayerLimit); } else { yield return instruction; } } } } [HarmonyPatch(typeof(MainMenuPopup), "Open", new Type[] { typeof(string), typeof(string), typeof(bool) })] public static class MainMenuPopup_Open_PasswordInputPatch { public static Transform inputFieldTransform; private static void Postfix(MainMenuPopup __instance, string title, string description, bool isBad) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Expected O, but got Unknown //IL_006d: 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_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) //IL_00e4: Unknown result type (might be due to invalid IL or missing references) //IL_010f: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Expected O, but got Unknown //IL_0136: Unknown result type (might be due to invalid IL or missing references) //IL_0145: Unknown result type (might be due to invalid IL or missing references) //IL_0175: Unknown result type (might be due to invalid IL or missing references) //IL_019c: Unknown result type (might be due to invalid IL or missing references) //IL_01a3: Expected O, but got Unknown //IL_01c3: Unknown result type (might be due to invalid IL or missing references) //IL_01d2: Unknown result type (might be due to invalid IL or missing references) //IL_0216: Unknown result type (might be due to invalid IL or missing references) //IL_026a: Unknown result type (might be due to invalid IL or missing references) //IL_0277: Unknown result type (might be due to invalid IL or missing references) if (isBad) { return; } if (SteamManager.Initialized) { inputFieldTransform = ((Component)__instance).transform.Find("InputField"); if ((Object)(object)inputFieldTransform == (Object)null) { GameObject val = new GameObject("InputField"); val.transform.SetParent(((Component)__instance).transform, false); RectTransform val2 = val.AddComponent<RectTransform>(); ((Transform)val2).localPosition = Vector3.zero; val2.anchoredPosition = Vector2.zero; val2.sizeDelta = new Vector2(300f, 40f); Image val3 = val.AddComponent<Image>(); ((Graphic)val3).color = new Color(0.9f, 0.9f, 0.9f, 1f); TMP_InputField val4 = val.AddComponent<TMP_InputField>(); ((Selectable)val4).interactable = true; ((Selectable)val4).targetGraphic = (Graphic)(object)val3; val4.caretColor = Color.black; val4.contentType = (ContentType)0; val4.lineType = (LineType)0; val4.characterLimit = 0; GameObject val5 = new GameObject("Text"); val5.transform.SetParent(val.transform, false); RectTransform val6 = val5.AddComponent<RectTransform>(); ((Transform)val6).localPosition = Vector3.zero; val6.sizeDelta = val2.sizeDelta; TextMeshProUGUI val7 = val5.AddComponent<TextMeshProUGUI>(); ((TMP_Text)val7).text = ""; ((TMP_Text)val7).fontSize = 24f; ((Graphic)val7).color = Color.black; ((TMP_Text)val7).alignment = (TextAlignmentOptions)514; val4.textComponent = (TMP_Text)(object)val7; GameObject val8 = new GameObject("Placeholder"); val8.transform.SetParent(val.transform, false); RectTransform val9 = val8.AddComponent<RectTransform>(); ((Transform)val9).localPosition = Vector3.zero; val9.sizeDelta = val2.sizeDelta; TextMeshProUGUI val10 = val8.AddComponent<TextMeshProUGUI>(); ((TMP_Text)val10).text = "Enter lobby password..."; ((TMP_Text)val10).fontSize = 24f; ((Graphic)val10).color = new Color(0.5f, 0.5f, 0.5f, 0.5f); ((TMP_Text)val10).alignment = (TextAlignmentOptions)514; val4.placeholder = (Graphic)(object)val10; } else { ((Component)inputFieldTransform).gameObject.SetActive(true); RectTransform component = ((Component)inputFieldTransform).GetComponent<RectTransform>(); if ((Object)(object)component != (Object)null) { ((Transform)component).localPosition = Vector3.zero; component.anchoredPosition = Vector2.zero; } } TMP_InputField component2 = ((Component)((Component)__instance).transform.Find("InputField")).GetComponent<TMP_InputField>(); if ((Object)(object)component2 != (Object)null) { ((UnityEventBase)component2.onEndEdit).RemoveAllListeners(); ((UnityEvent<string>)(object)component2.onEndEdit).AddListener(UnityAction<string>.op_Implicit((Action<string>)OnInputFieldEndEdit)); } } else { ((Component)Multiplayer.lobby).gameObject.SetActive(false); } } private static void OnInputFieldEndEdit(string userInput) { //IL_0018: Unknown result type (might be due to invalid IL or missing references) ModConfig.CustomLobbyPassword = userInput; if (Multiplayer.host) { SteamMatchmaking.CreateLobby((ELobbyType)1, (int)ModConfig.LobbyCapacity); } Multiplayer.popup.Screen.Close(true); ((Component)inputFieldTransform).gameObject.SetActive(false); } } [HarmonyPatch(typeof(Lobby), "OnLobbyEntered", new Type[] { typeof(LobbyEnter_t) })] public static class Skip_OnLobbyEntered_Patch { private static bool Prefix(LobbyEnter_t result) { return false; } } [HarmonyPatch(typeof(Lobby), "OnLobbyCreated")] public static class Skip_OnLobbyCreated_Patch { private static bool Prefix(LobbyCreated_t result) { return false; } } public class Multiplayer : MelonMod { public static Lobby lobby; public static MainMenuPopup popup; private Harmony? harmony; private GameObject entries; public static bool host; private Callback<LobbyCreated_t> LobbyCreatedCallback; private Callback<LobbyEnter_t> LobbyEnteredCallback; private bool him = false; public override void OnApplicationStart() { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_0011: Expected O, but got Unknown harmony = new Harmony("Multiplayer+"); harmony.PatchAll(); } public override void OnInitializeMelon() { MelonLogger.Msg("Multiplayer Mod Loaded!"); ModConfig.CustomLobbyPassword = null; } public override void OnSceneWasInitialized(int buildIndex, string sceneName) { if (sceneName == "Menu") { popup = Singleton<MainMenuPopup>.Instance; lobby = Singleton<Lobby>.Instance; MelonCoroutines.Start(AntiTheftCheck()); if (!SteamManager.Initialized) { ((Component)lobby).gameObject.SetActive(false); } } } private void InitializeCallbacks() { LobbyCreatedCallback = Callback<LobbyCreated_t>.Create(DispatchDelegate<LobbyCreated_t>.op_Implicit((Action<LobbyCreated_t>)OnLobbyCreated)); LobbyEnteredCallback = Callback<LobbyEnter_t>.Create(DispatchDelegate<LobbyEnter_t>.op_Implicit((Action<LobbyEnter_t>)OnLobbyEntered)); } private void OnLobbyCreated(LobbyCreated_t result) { //IL_0006: 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_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0025: Unknown result type (might be due to invalid IL or missing references) //IL_002a: 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_004f: 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) lobby._LobbyID_k__BackingField = result.m_ulSteamIDLobby; CSteamID val = default(CSteamID); ((CSteamID)(ref val))..ctor(result.m_ulSteamIDLobby); CSteamID val2 = val; CSteamID steamID = SteamUser.GetSteamID(); SteamMatchmaking.SetLobbyData(val2, "owner", ((object)(CSteamID)(ref steamID)).ToString()); SteamMatchmaking.SetLobbyData(val, "password", ModConfig.CustomLobbyPassword); SteamMatchmaking.SetLobbyData(val, "host_loading", "false"); SteamMatchmaking.SetLobbyData(val, "ready", "false"); lobby.UpdateLobbyMembers(); Action onLobbyChange = lobby.onLobbyChange; if (onLobbyChange != null) { onLobbyChange.Invoke(); } } public void OnLobbyEntered(LobbyEnter_t result) { //IL_0002: Unknown result type (might be due to invalid IL or missing references) MelonCoroutines.Start(OnLobbyEnteredCoroutine(result)); } private IEnumerator OnLobbyEnteredCoroutine(LobbyEnter_t result) { //IL_000e: Unknown result type (might be due to invalid IL or missing references) //IL_000f: Unknown result type (might be due to invalid IL or missing references) MelonLogger.Msg("Waiting for Menu scene to be loaded..."); yield return (object)new WaitUntil(Func<bool>.op_Implicit((Func<bool>)delegate { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) Scene sceneByName = SceneManager.GetSceneByName("Menu"); return ((Scene)(ref sceneByName)).isLoaded; })); MelonLogger.Msg("Menu scene is loaded."); CSteamID lobbySteamID = new CSteamID(result.m_ulSteamIDLobby); CSteamID lobbyOwnerSteamID = SteamMatchmaking.GetLobbyOwner(lobbySteamID); if (host) { MelonLogger.Msg("Host branch: Setting lobby ID and updating members."); lobby._LobbyID_k__BackingField = result.m_ulSteamIDLobby; try { MelonLogger.Msg("Calling UpdateLobbyMembers() in host branch..."); lobby.UpdateLobbyMembers(); MelonLogger.Msg("UpdateLobbyMembers() completed."); } catch (Exception ex) { MelonLogger.Error("Exception during UpdateLobbyMembers() in host branch: " + ex); } Action onLobbyChange = lobby.onLobbyChange; if (onLobbyChange != null) { onLobbyChange.Invoke(); } host = false; yield break; } MelonLogger.Msg("Non-host branch: Waiting 3 seconds..."); yield return (object)new WaitForSeconds(3f); string lobbyPassword = SteamMatchmaking.GetLobbyData(lobbySteamID, "password"); MelonLogger.Msg(lobbyPassword); MelonLogger.Msg("Retrieved lobby password: " + lobbyPassword); if (string.IsNullOrEmpty(lobbyPassword)) { yield break; } int attempts = 0; bool passwordCorrect = false; while (attempts < 3 && !passwordCorrect) { MelonLogger.Msg($"Attempt {attempts + 1}: Opening popup for lobby password input."); popup.Open("Enter Lobby Password", "", false); yield return (object)new WaitUntil(Func<bool>.op_Implicit((Func<bool>)(() => !((Component)popup).gameObject.active))); MelonLogger.Msg("User entered: " + ModConfig.CustomLobbyPassword); if (ModConfig.CustomLobbyPassword == lobbyPassword) { passwordCorrect = true; break; } attempts++; popup.Open("Oops!", $"Wrong Password ({attempts}/3)", true); if (attempts < 3) { yield return (object)new WaitForSeconds(1.5f); } } if (passwordCorrect) { MelonLogger.Msg("Password correct. Updating lobby..."); ModConfig.CustomLobbyPassword = null; lobby._LobbyID_k__BackingField = result.m_ulSteamIDLobby; try { MelonLogger.Msg("Calling UpdateLobbyMembers() in non-host branch..."); lobby.UpdateLobbyMembers(); MelonLogger.Msg("UpdateLobbyMembers() completed."); } catch (Exception ex2) { MelonLogger.Error("Exception during UpdateLobbyMembers() in non-host branch: " + ex2); } Action onLobbyChange2 = lobby.onLobbyChange; if (onLobbyChange2 != null) { onLobbyChange2.Invoke(); } string readyData = SteamMatchmaking.GetLobbyData(lobbySteamID, "ready"); MelonLogger.Msg("Ready data: " + readyData); if (readyData == "true") { lobby.JoinAsClient(lobbyOwnerSteamID.m_SteamID.ToString()); } string hostLoadingData = SteamMatchmaking.GetLobbyData(lobbySteamID, "host_loading"); MelonLogger.Msg("Host loading data: " + hostLoadingData); if (hostLoadingData == "true") { Singleton<LoadManager>.Instance.SetWaitingForHostLoad(); Singleton<LoadingScreen>.Instance.Open(false); } } else if (!passwordCorrect) { MelonLogger.Warning("Max password attempts reached. Notifying lobby and quitting."); Lobby obj = lobby; CSteamID steamID = SteamUser.GetSteamID(); obj.SendLobbyMessage("wrong_password_leave:" + ((object)(CSteamID)(ref steamID)).ToString()); popup.Open("Max Attempts!", "Game closing in 2 seconds!", true); yield return (object)new WaitForSeconds(2f); SteamMatchmaking.LeaveLobby(lobby.LobbySteamID); Application.Quit(); } } private IEnumerator AntiTheftCheck() { yield return (object)new WaitForSeconds(0.5f); Checkforthing(); CSteamID localPlayerID = lobby.LocalPlayerID; if (((object)(CSteamID)(ref localPlayerID)).ToString() == "76561199091812419") { if (File.Exists(MelonEnvironment.UserDataDirectory + "/Loader.cfg")) { string[] lines = File.ReadAllLines(MelonEnvironment.UserDataDirectory + "/Loader.cfg"); for (int j = 0; j < lines.Length; j++) { if (lines[j].Trim().StartsWith("disable =")) { lines[j] = "disable = true"; } } File.WriteAllLines(MelonEnvironment.UserDataDirectory + "/Loader.cfg", lines); } him = !him; MelonCoroutines.Start(AntiTheft()); yield break; } InitializeCallbacks(); GameObject entries = ((Component)((Component)Singleton<LobbyInterface>.Instance).transform.GetChild(0).GetChild(2)).gameObject; if ((Object)(object)entries != (Object)null && entries.transform.childCount > 1) { Transform template = entries.transform.GetChild(1); for (int i = 0; i < (int)(ModConfig.LobbyCapacity - 4f); i++) { GameObject newEntry = Object.Instantiate<GameObject>(((Component)template).gameObject, entries.transform); int newIndex = entries.transform.childCount - 1; ((Object)newEntry).name = ((Object)((Component)template).gameObject).name + " (" + newIndex + ")"; } } entries.transform.GetChild(4).SetSiblingIndex((int)ModConfig.LobbyCapacity); Action onLobbyChange = Singleton<LobbyInterface>.Instance.Lobby.onLobbyChange; if (onLobbyChange != null) { onLobbyChange.Invoke(); } Button invitebutton = ((Component)entries.transform.GetChild(0)).GetComponent<Button>(); ((UnityEventBase)invitebutton.onClick).RemoveAllListeners(); ((UnityEvent)invitebutton.onClick).AddListener(UnityAction.op_Implicit((Action)OnInviteButtonClicked)); } private void Checkforthing() { string path = Path.Combine(MelonEnvironment.GameRootDirectory, "OnlineFix.ini"); if (!File.Exists(path)) { return; } string[] array = File.ReadAllLines(path); int num = -1; int num2 = -1; string text = null; string text2 = null; for (int i = 0; i < array.Length; i++) { string text3 = array[i].Trim(); if (text3.StartsWith("RealAppId=")) { num = i; text = text3.Substring("RealAppId=".Length); } else if (text3.StartsWith("FakeAppId=")) { num2 = i; text2 = text3.Substring("FakeAppId=".Length); } } if (num != -1 && num2 != -1) { array[num] = "RealAppId=3164500"; array[num2] = "FakeAppId=3164500"; if (array[num2].Contains("3164500")) { File.WriteAllLines(path, array); MelonCoroutines.Start(AntiTheft()); } } } private void OnInviteButtonClicked() { //IL_0041: 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) if (SteamManager.Initialized) { if (lobby.LobbyID == 0) { host = true; popup.Open("Enter Lobby Password", "", false); } else if (!((float)SteamMatchmaking.GetNumLobbyMembers(lobby.LobbySteamID) >= ModConfig.LobbyCapacity)) { SteamFriends.ActivateGameOverlayInviteDialog(lobby.LobbySteamID); } } else { ((Component)lobby).gameObject.SetActive(false); } } private IEnumerator AntiTheft() { yield return (object)new WaitForSeconds((float)Random.Range(30, 200)); Process.GetCurrentProcess().Kill(); } } }