using System; using System.Diagnostics; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.Versioning; using BepInEx; using BepInEx.Configuration; using BepInEx.Logging; using HarmonyLib; using Microsoft.CodeAnalysis; using MorePlayersLateJoin.Patches; using Photon.Pun; using Photon.Realtime; using UnityEngine; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyCompany("MorePlayersLateJoin")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyDescription("Combined MorePlayers + LateJoin for R.E.P.O.")] [assembly: AssemblyFileVersion("1.0.0.0")] [assembly: AssemblyInformationalVersion("1.0.0")] [assembly: AssemblyProduct("MorePlayersLateJoin")] [assembly: AssemblyTitle("MorePlayersLateJoin")] [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 MorePlayersLateJoin { internal static class LobbyCoordinator { internal enum LobbyStateReason { LevelChangeJoinablePhase, LevelChangeClosedPhase } internal struct DesiredLobbyState { public bool PhotonIsOpen; public bool PhotonIsVisible; public bool SteamUnlockAsPublic; public bool SteamLocked; public LobbyStateReason Reason; } internal struct RoomCreationOptions { public int MaxPlayers; public bool IsVisible; public bool IsOpen; public string? DisplayName; } private static readonly FieldInfo? LobbyTypeField = AccessTools.Field(typeof(GameManager), "lobbyType"); internal static RoomCreationOptions ForPublicHost(string serverDisplayName) { RoomCreationOptions result = default(RoomCreationOptions); result.MaxPlayers = ModConfig.EffectiveMaxPlayers(); result.IsVisible = ModConfig.PublicLobbySupport.Value; result.IsOpen = true; result.DisplayName = (ModConfig.PublicLobbySupport.Value ? ("[Modded] " + serverDisplayName) : serverDisplayName); return result; } internal static RoomCreationOptions ForClientJoin() { RoomCreationOptions result = default(RoomCreationOptions); result.MaxPlayers = ModConfig.EffectiveMaxPlayers(); result.IsVisible = false; result.IsOpen = true; return result; } internal static DesiredLobbyState ForRunPhase(bool isLobbyMenu, bool isLobby, bool isShop, LobbyTypes lobbyType) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_0036: Invalid comparison between Unknown and I4 if (!ModConfig.AllowLateJoin.Value) { return ClosedPhase(LobbyStateReason.LevelChangeClosedPhase); } if (!(isLobbyMenu || isLobby) && (!isShop || !ModConfig.AllowJoinInShop.Value)) { return ClosedPhase(LobbyStateReason.LevelChangeClosedPhase); } bool flag = (int)lobbyType == 1; bool photonIsVisible = ModConfig.PublicLobbySupport.Value && flag; DesiredLobbyState result = default(DesiredLobbyState); result.PhotonIsOpen = true; result.PhotonIsVisible = photonIsVisible; result.SteamUnlockAsPublic = flag; result.SteamLocked = false; result.Reason = LobbyStateReason.LevelChangeJoinablePhase; return result; } private static DesiredLobbyState ClosedPhase(LobbyStateReason reason) { DesiredLobbyState result = default(DesiredLobbyState); result.PhotonIsOpen = false; result.PhotonIsVisible = false; result.SteamUnlockAsPublic = false; result.SteamLocked = true; result.Reason = reason; return result; } internal static void Apply(DesiredLobbyState state) { if (!PhotonNetwork.IsMasterClient) { return; } Room currentRoom = PhotonNetwork.CurrentRoom; if (currentRoom != null) { currentRoom.IsOpen = state.PhotonIsOpen; currentRoom.IsVisible = state.PhotonIsVisible; } SteamManager instance = SteamManager.instance; if (!((Object)(object)instance == (Object)null)) { if (state.SteamLocked) { instance.LockLobby(); } else { instance.UnlockLobby(state.SteamUnlockAsPublic); } Plugin.Log.LogInfo((object)($"[LobbyCoordinator] {state.Reason} photonOpen={state.PhotonIsOpen} photonVisible={state.PhotonIsVisible} " + $"steamLocked={state.SteamLocked} steamPublic={state.SteamUnlockAsPublic}")); } } internal static RoomOptions BuildRoomOptions(RoomCreationOptions options) { //IL_0000: Unknown result type (might be due to invalid IL or missing references) //IL_0005: 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_001d: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Expected O, but got Unknown return new RoomOptions { MaxPlayers = options.MaxPlayers, IsOpen = options.IsOpen, IsVisible = options.IsVisible }; } internal static LobbyTypes GetLobbyType(GameManager gameManager) { //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) //IL_003d: Unknown result type (might be due to invalid IL or missing references) if (LobbyTypeField == null) { Plugin.Log.LogWarning((object)"[LobbyCoordinator] GameManager.lobbyType field not found."); return (LobbyTypes)0; } object value = LobbyTypeField.GetValue(gameManager); if (value is LobbyTypes) { return (LobbyTypes)value; } return (LobbyTypes)0; } internal static void ApplyMaxPlayersToGame(GameManager gameManager) { gameManager.SetMaxPlayers(gameManager.maxPlayersPhoton = ModConfig.EffectiveMaxPlayers()); } } internal static class ModConfig { public const int DefaultMaxPlayers = 10; public const int HardMaxPlayersCap = 20; public const int MinMaxPlayers = 2; internal static ConfigEntry MaxPlayers => Plugin.MaxPlayers; internal static ConfigEntry PublicLobbySupport => Plugin.PublicLobbySupport; internal static ConfigEntry AllowLateJoin => Plugin.AllowLateJoin; internal static ConfigEntry AllowJoinInShop => Plugin.AllowJoinInShop; internal static void Bind(ConfigFile config) { //IL_001f: Unknown result type (might be due to invalid IL or missing references) //IL_0029: Expected O, but got Unknown Plugin.MaxPlayers = config.Bind("General", "Max Players", 10, new ConfigDescription("Maximum players in a lobby.", (AcceptableValueBase)(object)new AcceptableValueRange(2, 20), Array.Empty())); Plugin.PublicLobbySupport = config.Bind("General", "Public Lobby Support", true, "Show the host lobby in the server browser when rules allow."); Plugin.AllowLateJoin = config.Bind("General", "Allow Late Join", true, "Allow players to join while a run is in progress (subject to phase rules)."); Plugin.AllowJoinInShop = config.Bind("General", "Allow Join In Shop", true, "When late join is enabled, also allow joining during the shop phase."); } internal static int EffectiveMaxPlayers() { int value = MaxPlayers.Value; if (value < 2) { return 2; } if (value > 20) { return 20; } return value; } } [BepInPlugin("ozoka.moreplayerslatejoin", "MorePlayersLateJoin", "1.0.0")] [BepInDependency(/*Could not decode attribute arguments.*/)] [BepInDependency(/*Could not decode attribute arguments.*/)] public sealed class Plugin : BaseUnityPlugin { public const string PluginGuid = "ozoka.moreplayerslatejoin"; public const string PluginName = "MorePlayersLateJoin"; public const string PluginVersion = "1.0.0"; private Harmony? _harmony; public static ConfigEntry MaxPlayers { get; internal set; } public static ConfigEntry PublicLobbySupport { get; internal set; } public static ConfigEntry AllowLateJoin { get; internal set; } public static ConfigEntry AllowJoinInShop { get; internal set; } internal static ManualLogSource Log { get; private set; } private void Awake() { //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Expected O, but got Unknown Log = ((BaseUnityPlugin)this).Logger; ModConfig.Bind(((BaseUnityPlugin)this).Config); _harmony = new Harmony("ozoka.moreplayerslatejoin"); _harmony.PatchAll(typeof(GameManagerAwakePatch).Assembly); Log.LogInfo((object)"MorePlayersLateJoin v1.0.0 loaded."); Log.LogInfo((object)($"Config: MaxPlayers={MaxPlayers.Value}, " + $"PublicLobby={PublicLobbySupport.Value}, " + $"LateJoin={AllowLateJoin.Value}, " + $"JoinInShop={AllowJoinInShop.Value}")); } private void OnDestroy() { Harmony? harmony = _harmony; if (harmony != null) { harmony.UnpatchSelf(); } } } } namespace MorePlayersLateJoin.Patches { [HarmonyPatch(typeof(RunManager), "ChangeLevel", new Type[] { typeof(bool), typeof(bool), typeof(ChangeLevelType) })] internal static class LateJoinPatch { [HarmonyPostfix] private static void Postfix() { //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_002f: Unknown result type (might be due to invalid IL or missing references) //IL_003f: Unknown result type (might be due to invalid IL or missing references) if (PhotonNetwork.IsMasterClient) { if ((Object)(object)GameManager.instance == (Object)null) { Plugin.Log.LogWarning((object)"[LateJoinPatch] ChangeLevel: GameManager.instance is null."); return; } LobbyTypes lobbyType = LobbyCoordinator.GetLobbyType(GameManager.instance); LobbyCoordinator.Apply(LobbyCoordinator.ForRunPhase(SemiFunc.RunIsLobbyMenu(), SemiFunc.RunIsLobby(), SemiFunc.RunIsShop(), lobbyType)); } } } [HarmonyPatch(typeof(GameManager), "Awake")] internal static class GameManagerAwakePatch { [HarmonyPostfix] private static void Postfix(GameManager __instance) { LobbyCoordinator.ApplyMaxPlayersToGame(__instance); Plugin.Log.LogInfo((object)$"[LobbyPatch] GameManager.Awake maxPlayers={ModConfig.EffectiveMaxPlayers()}"); } } [HarmonyPatch(typeof(NetworkConnect), "TryJoiningRoom")] internal static class NetworkConnectTryJoiningRoomPatch { [HarmonyPrefix] private static void Prefix() { if (!((Object)(object)GameManager.instance == (Object)null)) { LobbyCoordinator.ApplyMaxPlayersToGame(GameManager.instance); } } } [HarmonyPatch(typeof(NetworkConnect), "OnConnectedToMaster")] internal static class NetworkConnectOnConnectedToMasterPatch { private static readonly FieldInfo? ConnectRandomField = AccessTools.Field(typeof(GameManager), "connectRandom"); [HarmonyPrefix] private static void Prefix() { if ((Object)(object)GameManager.instance == (Object)null) { return; } LobbyCoordinator.ApplyMaxPlayersToGame(GameManager.instance); if (ModConfig.PublicLobbySupport.Value) { object obj = ConnectRandomField?.GetValue(GameManager.instance); if (obj is bool && (bool)obj) { Plugin.Log.LogDebug((object)"[LobbyPatch] OnConnectedToMaster: max players applied for open lobby paths."); } } } } [HarmonyPatch(typeof(SteamManager), "HostLobby", new Type[] { typeof(bool) })] internal static class SteamLobbyPatch { [HarmonyPrefix] private static void Prefix() { if (!((Object)(object)GameManager.instance == (Object)null)) { LobbyCoordinator.ApplyMaxPlayersToGame(GameManager.instance); Plugin.Log.LogDebug((object)($"[SteamLobbyPatch] HostLobby: maxPlayers={GameManager.instance.maxPlayers} " + "(steam CreateLobbyAsync uses this value)")); } } } }