Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of BunnyHopper v1.1.0
BunnyHopper.Il2Cpp.dll
Decompiled 2 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BunnyHopper; using BunnyHopper.Models; using HarmonyLib; using Il2CppScheduleOne.DevUtilities; using Il2CppScheduleOne.PlayerScripts; using Il2CppScheduleOne.UI.Settings; using Il2CppSystem.Collections; using MelonLoader; using MelonLoader.Preferences; using UnityEngine; using UnityEngine.InputSystem; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: ComVisible(true)] [assembly: Guid("78217591-6296-4A47-9657-F90E4322F66F")] [assembly: MelonInfo(typeof(Main), "Bunny Hopper", "1.1.0", "Roach_ (Adrian Nicolae)", "https://github.com/RoachxD/ScheduleOne.BunnyHopper/releases/latest")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonColor(255, 212, 172, 45)] [assembly: AssemblyMetadata("NexusModID", "1033")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("Roach_")] [assembly: AssemblyConfiguration("Release_Il2Cpp")] [assembly: AssemblyCopyright("Copyright © 2025 Roach_ (Adrian Nicolae)")] [assembly: AssemblyDescription("A MelonLoader mod for Schedule I that enables \"true\" auto-jumping/bunny-hopping.")] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0")] [assembly: AssemblyProduct("BunnyHopper.Il2Cpp")] [assembly: AssemblyTitle("BunnyHopper.Il2Cpp")] [assembly: NeutralResourcesLanguage("en-GB")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.0.0")] [module: UnverifiableCode] namespace BunnyHopper { public class Main : MelonMod { [HarmonyPatch(typeof(PlayerMovement), "Move")] private static class PlayerMovementMovePatch { private static Func<PlayerMovement, IEnumerator> _invokeJumpMethod; private static Func<PlayerMovement, bool> _getIsJumpingValue; static PlayerMovementMovePatch() { Type typeFromHandle = typeof(PlayerMovement); MethodInfo method = typeFromHandle.GetMethod("Jump", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { try { ParameterExpression parameterExpression = Expression.Parameter(typeFromHandle, "playerMovement"); _invokeJumpMethod = Expression.Lambda<Func<PlayerMovement, IEnumerator>>(Expression.Call(parameterExpression, method), new ParameterExpression[1] { parameterExpression }).Compile(); } catch (Exception ex) { Melon<Main>.Logger.Error("Failed to compile delegate for Jump method: " + ex.Message + ". Auto-jumping will not function."); } } else { Melon<Main>.Logger.Error("Reflection: PlayerMovement.Jump method not found. Auto-jumping will not function."); } MemberInfo memberInfo = (MemberInfo)(((object)typeof(PlayerMovement).GetField("isJumping", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) ?? ((object)typeof(PlayerMovement).GetProperty("isJumping", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))); if (memberInfo != null) { try { ParameterExpression parameterExpression2 = Expression.Parameter(typeFromHandle, "playerMovement"); _getIsJumpingValue = Expression.Lambda<Func<PlayerMovement, bool>>(Expression.MakeMemberAccess(parameterExpression2, memberInfo), new ParameterExpression[1] { parameterExpression2 }).Compile(); return; } catch (Exception ex2) { Melon<Main>.Logger.Error("Failed to compile delegate for isJumping member: " + ex2.Message + ". Auto-jump condition check might be impaired."); return; } } Melon<Main>.Logger.Error("Reflection: PlayerMovement.isJumping member (field/property) not found. Auto-jump condition check might be impaired."); } public static void Prefix(PlayerMovement __instance) { if (!ShouldSkipPatch(__instance)) { if (currentPlayerJumpState == null) { currentPlayerJumpState = new PlayerJumpState(); } currentPlayerJumpState.WasGroundedBeforeMove = __instance.Controller.isGrounded; } } public static void Postfix(PlayerMovement __instance) { if (ShouldSkipPatch(__instance)) { return; } if (currentPlayerJumpState == null) { Melon<Main>.Logger.Error("[PlayerMovement.Move.Postfix] Critical: currentPlayerJumpState is null. Aborting Postfix logic."); return; } bool isGrounded = __instance.Controller.isGrounded; if (currentPlayerJumpState.AwaitingLiftoffAfterAutoJump) { if (!isGrounded) { currentPlayerJumpState.AwaitingLiftoffAfterAutoJump = false; currentPlayerJumpState.ModInitiatedCurrentJump = true; } return; } bool wasGroundedBeforeMove = currentPlayerJumpState.WasGroundedBeforeMove; bool modInitiatedCurrentJump = currentPlayerJumpState.ModInitiatedCurrentJump; if (!wasGroundedBeforeMove && isGrounded) { if (modInitiatedCurrentJump) { currentPlayerJumpState.ModInitiatedCurrentJump = false; } if (ShouldAutoJump(__instance)) { MelonCoroutines.Start(ExecuteAutoJumpCoroutine(__instance, currentPlayerJumpState)); } } } private static IEnumerator ExecuteAutoJumpCoroutine(PlayerMovement playerMovement, PlayerJumpState jumpState) { if ((Object)(object)playerMovement == (Object)null || (Object)(object)playerMovement.Controller == (Object)null || jumpState.AwaitingLiftoffAfterAutoJump) { yield break; } jumpState.AwaitingLiftoffAfterAutoJump = true; jumpState.ModInitiatedCurrentJump = false; yield return null; if (ShouldAutoJump(playerMovement)) { if (_invokeJumpMethod != null) { IEnumerator val = _invokeJumpMethod(playerMovement); ((MonoBehaviour)playerMovement).StartCoroutine(val); MelonCoroutines.Start(MonitorLiftoff(playerMovement, jumpState)); } else { Melon<Main>.Logger.Error("[ExecuteAutoJumpCoro] Jump method delegate is null. Cannot execute jump."); jumpState.AwaitingLiftoffAfterAutoJump = false; } } else { jumpState.AwaitingLiftoffAfterAutoJump = false; } } private static IEnumerator MonitorLiftoff(PlayerMovement playerMovement, PlayerJumpState jumpState) { float num = (float)AutoJumpLiftoffTimeoutMilliseconds.Value / 1000f; float endTime = Time.time + num; while (Time.time < endTime) { if ((Object)(object)playerMovement == (Object)null || (Object)(object)playerMovement.Controller == (Object)null || !jumpState.AwaitingLiftoffAfterAutoJump) { yield break; } yield return null; } if ((Object)(object)playerMovement != (Object)null && (Object)(object)playerMovement.Controller != (Object)null && jumpState.AwaitingLiftoffAfterAutoJump) { if (playerMovement.Controller.isGrounded) { jumpState.AwaitingLiftoffAfterAutoJump = false; } else { jumpState.AwaitingLiftoffAfterAutoJump = false; } } } private static bool ShouldAutoJump(PlayerMovement playerMovement) { if (!jumpActionReference.action.IsPressed() || (!playerMovement.IsGrounded && !playerMovement.Controller.isGrounded) || !playerMovement.canJump) { return false; } try { return !_getIsJumpingValue(playerMovement); } catch (Exception ex) { Melon<Main>.Logger.Error("Error getting PlayerMovement.isJumping via reflection: " + ex.Message); } return false; } private static bool ShouldSkipPatch(PlayerMovement playerMovement) { if (Enabled.Value && _invokeJumpMethod != null && _getIsJumpingValue != null && !((Object)(object)jumpActionReference == (Object)null) && jumpActionReference.action != null && !((Object)(object)playerMovement == (Object)null)) { return (Object)(object)playerMovement.Controller == (Object)null; } return true; } } private static InputActionReference jumpActionReference; private static PlayerJumpState currentPlayerJumpState; public static MelonPreferences_Category SettingsCategory { get; private set; } public static MelonPreferences_Entry<bool> Enabled { get; private set; } public static MelonPreferences_Entry<int> AutoJumpLiftoffTimeoutMilliseconds { get; private set; } public override void OnInitializeMelon() { SettingsCategory = MelonPreferences.CreateCategory("BunnyHopper_Settings", "Bunny Hopper Settings"); Enabled = SettingsCategory.CreateEntry<bool>("Enabled", true, "Enable or disable the bunny hop feature.", (string)null, false, false, (ValueValidator)null, (string)null); AutoJumpLiftoffTimeoutMilliseconds = SettingsCategory.CreateEntry<int>("AutoJumpLiftoffTimeout", 75, "Liftoff timeout (ms) before auto-jump is considered failed.", (string)null, false, false, (ValueValidator)(object)new ValueRange<int>(55, 110), (string)null); string value = "IL2CPP"; Melon<Main>.Logger.Msg($"Bunny Hopper ({value}) initializing with following settings: Enabled={Enabled.Value}, AutoJumpLiftoffTimeoutMilliseconds={AutoJumpLiftoffTimeoutMilliseconds.Value}."); try { ((MelonBase)this).HarmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); IEnumerable<string> values = from p in ((MelonBase)this).HarmonyInstance.GetPatchedMethods() select p.DeclaringType.FullName + "." + p.Name; string text = string.Join(", ", values); Melon<Main>.Logger.Msg("Harmony patches successfully applied: " + text + "."); } catch (Exception value2) { Melon<Main>.Logger.Error($"Error during Harmony's PatchAll execution: {value2}"); } } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (Enabled.Value && !((Object)(object)jumpActionReference != (Object)null) && !(sceneName != "Menu")) { Melon<Main>.Logger.Msg("Main menu was loaded, attempting to detect Jump Action reference.."); DetectJumpAction(); } } private static void DetectJumpAction() { //IL_0100: Unknown result type (might be due to invalid IL or missing references) GameObject val = GameObject.Find("MainMenu"); if ((Object)(object)val == (Object)null) { Melon<Main>.Logger.Error("Main Menu not found, unable to detect Jump Action!"); return; } Transform val2 = val.transform.Find("Settings/Content/Controls/Row/Jump"); if ((Object)(object)val2 == (Object)null) { Melon<Main>.Logger.Error("Jump control UI element not found, unable to detect Jump Action!"); return; } Keybinder component = ((Component)val2).GetComponent<Keybinder>(); if ((Object)(object)component == (Object)null) { Melon<Main>.Logger.Error("Keybinder component not found on Jump control, unable to detect Jump Action!"); return; } RebindActionUI rebindActionUI = component.rebindActionUI; if ((Object)(object)rebindActionUI == (Object)null) { Melon<Main>.Logger.Error("Rebind Action UI is null on Keybinder component, unable to detect Jump Action!"); return; } InputActionReference actionReference = rebindActionUI.actionReference; if ((Object)(object)actionReference == (Object)null) { Melon<Main>.Logger.Error("Jump Action reference is null on Rebind Action UI!"); return; } jumpActionReference = actionReference; Melon<Main>.Logger.Msg($"Successfully auto-detected Jump Action: Name={jumpActionReference.action.name}, ID={jumpActionReference.action.id}."); } } } namespace BunnyHopper.Models { public class PlayerJumpState { public bool WasGroundedBeforeMove { get; set; } public bool ModInitiatedCurrentJump { get; set; } public bool AwaitingLiftoffAfterAutoJump { get; set; } } }
BunnyHopper.Mono.dll
Decompiled 2 weeks agousing System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Linq.Expressions; using System.Reflection; using System.Resources; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Permissions; using BunnyHopper; using BunnyHopper.Models; using HarmonyLib; using MelonLoader; using MelonLoader.Preferences; using ScheduleOne.DevUtilities; using ScheduleOne.PlayerScripts; using ScheduleOne.UI.Settings; using UnityEngine; using UnityEngine.InputSystem; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: ComVisible(true)] [assembly: Guid("78217591-6296-4A47-9657-F90E4322F66F")] [assembly: MelonInfo(typeof(Main), "Bunny Hopper", "1.1.0", "Roach_ (Adrian Nicolae)", "https://github.com/RoachxD/ScheduleOne.BunnyHopper/releases/latest")] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: MelonColor(255, 212, 172, 45)] [assembly: AssemblyMetadata("NexusModID", "1033")] [assembly: TargetFramework(".NETFramework,Version=v4.7.2", FrameworkDisplayName = ".NET Framework 4.7.2")] [assembly: AssemblyCompany("Roach_")] [assembly: AssemblyConfiguration("Release_Mono")] [assembly: AssemblyCopyright("Copyright © 2025 Roach_ (Adrian Nicolae)")] [assembly: AssemblyDescription("A MelonLoader mod for Schedule I that enables \"true\" auto-jumping/bunny-hopping.")] [assembly: AssemblyFileVersion("1.1.0.0")] [assembly: AssemblyInformationalVersion("1.1.0")] [assembly: AssemblyProduct("BunnyHopper.Mono")] [assembly: AssemblyTitle("BunnyHopper.Mono")] [assembly: NeutralResourcesLanguage("en-GB")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("1.1.0.0")] [module: UnverifiableCode] namespace BunnyHopper { public class Main : MelonMod { [HarmonyPatch(typeof(PlayerMovement), "Move")] private static class PlayerMovementMovePatch { private static Func<PlayerMovement, IEnumerator> _invokeJumpMethod; private static Func<PlayerMovement, bool> _getIsJumpingValue; static PlayerMovementMovePatch() { Type typeFromHandle = typeof(PlayerMovement); MethodInfo method = typeFromHandle.GetMethod("Jump", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); if (method != null) { try { ParameterExpression parameterExpression = Expression.Parameter(typeFromHandle, "playerMovement"); _invokeJumpMethod = Expression.Lambda<Func<PlayerMovement, IEnumerator>>(Expression.Call(parameterExpression, method), new ParameterExpression[1] { parameterExpression }).Compile(); } catch (Exception ex) { Melon<Main>.Logger.Error("Failed to compile delegate for Jump method: " + ex.Message + ". Auto-jumping will not function."); } } else { Melon<Main>.Logger.Error("Reflection: PlayerMovement.Jump method not found. Auto-jumping will not function."); } MemberInfo memberInfo = (MemberInfo)(((object)typeof(PlayerMovement).GetField("isJumping", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic)) ?? ((object)typeof(PlayerMovement).GetProperty("isJumping", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic))); if (memberInfo != null) { try { ParameterExpression parameterExpression2 = Expression.Parameter(typeFromHandle, "playerMovement"); _getIsJumpingValue = Expression.Lambda<Func<PlayerMovement, bool>>(Expression.MakeMemberAccess(parameterExpression2, memberInfo), new ParameterExpression[1] { parameterExpression2 }).Compile(); return; } catch (Exception ex2) { Melon<Main>.Logger.Error("Failed to compile delegate for isJumping member: " + ex2.Message + ". Auto-jump condition check might be impaired."); return; } } Melon<Main>.Logger.Error("Reflection: PlayerMovement.isJumping member (field/property) not found. Auto-jump condition check might be impaired."); } public static void Prefix(PlayerMovement __instance) { if (!ShouldSkipPatch(__instance)) { if (currentPlayerJumpState == null) { currentPlayerJumpState = new PlayerJumpState(); } currentPlayerJumpState.WasGroundedBeforeMove = __instance.Controller.isGrounded; } } public static void Postfix(PlayerMovement __instance) { if (ShouldSkipPatch(__instance)) { return; } if (currentPlayerJumpState == null) { Melon<Main>.Logger.Error("[PlayerMovement.Move.Postfix] Critical: currentPlayerJumpState is null. Aborting Postfix logic."); return; } bool isGrounded = __instance.Controller.isGrounded; if (currentPlayerJumpState.AwaitingLiftoffAfterAutoJump) { if (!isGrounded) { currentPlayerJumpState.AwaitingLiftoffAfterAutoJump = false; currentPlayerJumpState.ModInitiatedCurrentJump = true; } return; } bool wasGroundedBeforeMove = currentPlayerJumpState.WasGroundedBeforeMove; bool modInitiatedCurrentJump = currentPlayerJumpState.ModInitiatedCurrentJump; if (!wasGroundedBeforeMove && isGrounded) { if (modInitiatedCurrentJump) { currentPlayerJumpState.ModInitiatedCurrentJump = false; } if (ShouldAutoJump(__instance)) { MelonCoroutines.Start(ExecuteAutoJumpCoroutine(__instance, currentPlayerJumpState)); } } } private static IEnumerator ExecuteAutoJumpCoroutine(PlayerMovement playerMovement, PlayerJumpState jumpState) { if ((Object)(object)playerMovement == (Object)null || (Object)(object)playerMovement.Controller == (Object)null || jumpState.AwaitingLiftoffAfterAutoJump) { yield break; } jumpState.AwaitingLiftoffAfterAutoJump = true; jumpState.ModInitiatedCurrentJump = false; yield return null; if (ShouldAutoJump(playerMovement)) { if (_invokeJumpMethod != null) { IEnumerator enumerator = _invokeJumpMethod(playerMovement); ((MonoBehaviour)playerMovement).StartCoroutine(enumerator); MelonCoroutines.Start(MonitorLiftoff(playerMovement, jumpState)); } else { Melon<Main>.Logger.Error("[ExecuteAutoJumpCoro] Jump method delegate is null. Cannot execute jump."); jumpState.AwaitingLiftoffAfterAutoJump = false; } } else { jumpState.AwaitingLiftoffAfterAutoJump = false; } } private static IEnumerator MonitorLiftoff(PlayerMovement playerMovement, PlayerJumpState jumpState) { float num = (float)AutoJumpLiftoffTimeoutMilliseconds.Value / 1000f; float endTime = Time.time + num; while (Time.time < endTime) { if ((Object)(object)playerMovement == (Object)null || (Object)(object)playerMovement.Controller == (Object)null || !jumpState.AwaitingLiftoffAfterAutoJump) { yield break; } yield return null; } if ((Object)(object)playerMovement != (Object)null && (Object)(object)playerMovement.Controller != (Object)null && jumpState.AwaitingLiftoffAfterAutoJump) { if (playerMovement.Controller.isGrounded) { jumpState.AwaitingLiftoffAfterAutoJump = false; } else { jumpState.AwaitingLiftoffAfterAutoJump = false; } } } private static bool ShouldAutoJump(PlayerMovement playerMovement) { if (!jumpActionReference.action.IsPressed() || (!playerMovement.IsGrounded && !playerMovement.Controller.isGrounded) || !playerMovement.canJump) { return false; } try { return !_getIsJumpingValue(playerMovement); } catch (Exception ex) { Melon<Main>.Logger.Error("Error getting PlayerMovement.isJumping via reflection: " + ex.Message); } return false; } private static bool ShouldSkipPatch(PlayerMovement playerMovement) { if (Enabled.Value && _invokeJumpMethod != null && _getIsJumpingValue != null && !((Object)(object)jumpActionReference == (Object)null) && jumpActionReference.action != null && !((Object)(object)playerMovement == (Object)null)) { return (Object)(object)playerMovement.Controller == (Object)null; } return true; } } private static InputActionReference jumpActionReference; private static PlayerJumpState currentPlayerJumpState; public static MelonPreferences_Category SettingsCategory { get; private set; } public static MelonPreferences_Entry<bool> Enabled { get; private set; } public static MelonPreferences_Entry<int> AutoJumpLiftoffTimeoutMilliseconds { get; private set; } public override void OnInitializeMelon() { SettingsCategory = MelonPreferences.CreateCategory("BunnyHopper_Settings", "Bunny Hopper Settings"); Enabled = SettingsCategory.CreateEntry<bool>("Enabled", true, "Enable or disable the bunny hop feature.", (string)null, false, false, (ValueValidator)null, (string)null); AutoJumpLiftoffTimeoutMilliseconds = SettingsCategory.CreateEntry<int>("AutoJumpLiftoffTimeout", 75, "Liftoff timeout (ms) before auto-jump is considered failed.", (string)null, false, false, (ValueValidator)(object)new ValueRange<int>(55, 110), (string)null); string arg = "Mono"; Melon<Main>.Logger.Msg($"Bunny Hopper ({arg}) initializing with following settings: Enabled={Enabled.Value}, AutoJumpLiftoffTimeoutMilliseconds={AutoJumpLiftoffTimeoutMilliseconds.Value}."); try { ((MelonBase)this).HarmonyInstance.PatchAll(Assembly.GetExecutingAssembly()); IEnumerable<string> values = from p in ((MelonBase)this).HarmonyInstance.GetPatchedMethods() select p.DeclaringType.FullName + "." + p.Name; string text = string.Join(", ", values); Melon<Main>.Logger.Msg("Harmony patches successfully applied: " + text + "."); } catch (Exception arg2) { Melon<Main>.Logger.Error($"Error during Harmony's PatchAll execution: {arg2}"); } } public override void OnSceneWasLoaded(int buildIndex, string sceneName) { if (Enabled.Value && !((Object)(object)jumpActionReference != (Object)null) && !(sceneName != "Menu")) { Melon<Main>.Logger.Msg("Main menu was loaded, attempting to detect Jump Action reference.."); DetectJumpAction(); } } private static void DetectJumpAction() { GameObject val = GameObject.Find("MainMenu"); if ((Object)(object)val == (Object)null) { Melon<Main>.Logger.Error("Main Menu not found, unable to detect Jump Action!"); return; } Transform val2 = val.transform.Find("Settings/Content/Controls/Row/Jump"); if ((Object)(object)val2 == (Object)null) { Melon<Main>.Logger.Error("Jump control UI element not found, unable to detect Jump Action!"); return; } Keybinder component = ((Component)val2).GetComponent<Keybinder>(); if ((Object)(object)component == (Object)null) { Melon<Main>.Logger.Error("Keybinder component not found on Jump control, unable to detect Jump Action!"); return; } RebindActionUI rebindActionUI = component.rebindActionUI; if ((Object)(object)rebindActionUI == (Object)null) { Melon<Main>.Logger.Error("Rebind Action UI is null on Keybinder component, unable to detect Jump Action!"); return; } InputActionReference actionReference = rebindActionUI.actionReference; if ((Object)(object)actionReference == (Object)null) { Melon<Main>.Logger.Error("Jump Action reference is null on Rebind Action UI!"); return; } jumpActionReference = actionReference; Melon<Main>.Logger.Msg($"Successfully auto-detected Jump Action: Name={jumpActionReference.action.name}, ID={jumpActionReference.action.id}."); } } } namespace BunnyHopper.Models { public class PlayerJumpState { public bool WasGroundedBeforeMove { get; set; } public bool ModInitiatedCurrentJump { get; set; } public bool AwaitingLiftoffAfterAutoJump { get; set; } } }