Skip to content

Commit

Permalink
Reimplemented Unity Mono Support
Browse files Browse the repository at this point in the history
  • Loading branch information
HerpDerpinstine committed Feb 7, 2025
1 parent 636345b commit 24f6d7b
Show file tree
Hide file tree
Showing 13 changed files with 477 additions and 21 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,6 @@
<DebugType>embedded</DebugType>
<AssemblyName>MelonLoader.Engine.Unity.Shared</AssemblyName>
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="MelonLoader.Engine.Unity" />
<InternalsVisibleTo Include="MelonLoader.Unity.Il2Cpp" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\classdata.tpk" />
<ProjectReference Include="$(SolutionDir)\MelonLoader\MelonLoader.csproj" ExcludeAssets="all" Private="False">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,23 @@

namespace MelonLoader
{
internal abstract class MelonCoroutineInterop
public abstract class MelonCoroutineInterop
{
internal abstract object Start(IEnumerator routine);
internal abstract void Stop(object coroutineToken);
public abstract object Start(IEnumerator routine);
public abstract void Stop(object coroutineToken);
}

public static class MelonCoroutines
{
internal static List<IEnumerator> QueuedCoroutines = new List<IEnumerator>();
internal static MelonCoroutineInterop Interop;
private static List<IEnumerator> QueuedCoroutines = new List<IEnumerator>();
private static bool HasProcessedQueue;
public static MelonCoroutineInterop Interop;

internal static void ProcessQueue()
public static void ProcessQueue()
{
if (HasProcessedQueue)
return;
HasProcessedQueue = true;
foreach (var queuedCoroutine in QueuedCoroutines)
Start(queuedCoroutine);
QueuedCoroutines.Clear();
Expand All @@ -31,6 +35,17 @@ public static object Start(IEnumerator routine)
{
if (Interop != null)
return Interop.Start(routine);
return Queue(routine);
}

/// <summary>
/// Start a new coroutine.<br />
/// Coroutines are called at the end of the game Update loops.
/// </summary>
/// <param name="routine">The target routine</param>
/// <returns>An object that can be passed to Stop to stop this coroutine</returns>
public static object Queue(IEnumerator routine)
{
QueuedCoroutines.Add(routine);
return routine;
}
Expand All @@ -43,8 +58,14 @@ public static void Stop(object coroutineToken)
{
if (Interop != null)
Interop.Stop(coroutineToken);
else
QueuedCoroutines.Remove(coroutineToken as IEnumerator);
Dequeue(coroutineToken as IEnumerator);
}

public static void Dequeue(object coroutineToken)
{
IEnumerator routine = coroutineToken as IEnumerator;
if (QueuedCoroutines.Contains(routine))
QueuedCoroutines.Remove(routine);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ private static UnityVersion TryParse(string version)
return returnval;
}

internal static void Setup(string gameDataPath, string unityPlayerPath)
public static void Setup(string gameDataPath, string unityPlayerPath)
{
//if (!string.IsNullOrEmpty(LoaderConfig.Current.UnityEngine.VersionOverride))
// EngineVersion = TryParse(LoaderConfig.Current.UnityEngine.VersionOverride);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,20 @@ internal class Il2CppCoroutineInterop : MelonCoroutineInterop
internal Il2CppCoroutineInterop(Il2CppSupportComponent component)
=> Component = component;

internal override object Start(IEnumerator coroutine)
public override object Start(IEnumerator coroutine)
{
if (Component != null)
return Component.StartCoroutine(new Il2CppSystem.Collections.IEnumerator(new Il2CppEnumeratorWrapper(coroutine).Pointer));

MelonCoroutines.QueuedCoroutines.Add(coroutine);
MelonCoroutines.Queue(coroutine);
return coroutine;
}

internal override void Stop(object coroutineToken)
public override void Stop(object coroutineToken)
{
if (Component != null)
Component.StopCoroutine(coroutineToken as Coroutine);
else
MelonCoroutines.QueuedCoroutines.Remove(coroutineToken as IEnumerator);
MelonCoroutines.Dequeue(coroutineToken);
}
}
}
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using System.Collections;
using UnityEngine;

namespace MelonLoader.Engine.Unity.Mono
{
internal class MonoCoroutineInterop : MelonCoroutineInterop
{
private MonoSupportComponent Component;

internal MonoCoroutineInterop(MonoSupportComponent component)
=> Component = component;

public override object Start(IEnumerator coroutine)
{
if (Component != null)
return Component.StartCoroutine(coroutine);
return MelonCoroutines.Start(coroutine);
}

public override void Stop(object coroutineToken)
{
if (Component != null)
Component.StopCoroutine(coroutineToken as Coroutine);
else
MelonCoroutines.Stop(coroutineToken);
}
}
}
100 changes: 100 additions & 0 deletions Dependencies/Modules/Engines/Unity/Mono/Unity.Mono/MonoSceneHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
using System;
using UnityEngine.SceneManagement;
using System.Collections.Generic;

namespace MelonLoader.Engine.Unity.Mono
{
internal class MonoSceneHandler : IDisposable
{
private MonoSupportModule SupportModule;

internal class SceneInitEvent
{
internal int buildIndex;
internal string name;
internal bool wasLoadedThisTick;
}
private Queue<SceneInitEvent> scenesLoaded = new Queue<SceneInitEvent>();

internal MonoSceneHandler(MonoSupportModule supportModule)
{
SupportModule = supportModule;

try
{
SceneManager.sceneLoaded += OnSceneLoad;
}
catch (Exception ex) { MelonLogger.Error($"SceneManager.sceneLoaded override failed: {ex}"); }

try
{
SceneManager.sceneUnloaded += OnSceneUnload;
}
catch (Exception ex) { MelonLogger.Error($"SceneManager.sceneUnloaded override failed: {ex}"); }
}

~MonoSceneHandler()
=> Dispose();

[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2013:Do not use ReferenceEquals with value types")]
public void Dispose()
{
try
{
SceneManager.sceneLoaded -= OnSceneLoad;
}
catch (Exception ex) { MelonLogger.Error($"SceneManager.sceneLoaded override failed: {ex}"); }

try
{
SceneManager.sceneUnloaded -= OnSceneUnload;
}
catch (Exception ex) { MelonLogger.Error($"SceneManager.sceneUnloaded override failed: {ex}"); }
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2013:Do not use ReferenceEquals with value types")]
private void OnSceneLoad(Scene scene, LoadSceneMode mode)
{
if (SupportModule.obj == null)
SupportModule.CreateGameObject();

if (ReferenceEquals(scene, null))
return;

MelonUnityEvents.OnSceneWasLoaded.Invoke(scene.buildIndex, scene.name);
scenesLoaded.Enqueue(new SceneInitEvent { buildIndex = scene.buildIndex, name = scene.name });
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Reliability", "CA2013:Do not use ReferenceEquals with value types")]
private void OnSceneUnload(Scene scene)
{
if (ReferenceEquals(scene, null))
return;

MelonUnityEvents.OnSceneWasUnloaded.Invoke(scene.buildIndex, scene.name);
}

internal void OnUpdate()
{
if (scenesLoaded.Count > 0)
{
Queue<SceneInitEvent> requeue = new Queue<SceneInitEvent>();
SceneInitEvent evt = null;
while ((scenesLoaded.Count > 0) && ((evt = scenesLoaded.Dequeue()) != null))
{
if (evt.wasLoadedThisTick)
{
MelonUnityEvents.OnSceneWasInitialized.Invoke(evt.buildIndex, evt.name);
}
else
{
evt.wasLoadedThisTick = true;
requeue.Enqueue(evt);
}
}
while ((requeue.Count > 0) && ((evt = requeue.Dequeue()) != null))
scenesLoaded.Enqueue(evt);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
using System;
using System.Reflection;
using UnityEngine;
using MelonLoader.Modules;

namespace MelonLoader.Engine.Unity.Mono
{
internal class MonoSupportComponent : MonoBehaviour
{
private bool isQuitting;
private bool hadError;

private MethodInfo SetAsLastSiblingMethod;

MonoSupportComponent()
{
try
{
SetAsLastSiblingMethod = typeof(Transform).GetMethod("SetAsLastSibling", BindingFlags.Public | BindingFlags.Instance);
if (SetAsLastSiblingMethod == null)
throw new Exception("Unable to find UnityEngine.Transform::SetAsLastSibling");
}
catch (Exception ex) { LogError("Getting UnityEngine.Transform::SetAsLastSibling", ex); }
}

private void LogError(string cat, Exception ex)
{
hadError = true;
MelonLogger.Warning($"Exception while {cat}: {ex}");
MelonLogger.Warning("Melon Events might run before some MonoBehaviour Events");
}

internal void SiblingFix()
{
if (hadError)
return;

try
{
gameObject.transform.SetAsLastSibling();
transform.SetAsLastSibling();
}
catch (Exception ex)
{
LogError("Invoking UnityEngine.Transform::SetAsLastSibling", ex);
}
}

void Start()
{
if ((ModuleInterop.Support == null) || (((MonoSupportModule)ModuleInterop.Support).component != this))
return;

SiblingFix();

MelonUnityEvents.OnApplicationLateStart.Invoke();
}

void Awake()
{
if ((ModuleInterop.Support == null) || (((MonoSupportModule)ModuleInterop.Support).component != this))
return;

MelonCoroutines.ProcessQueue();
}

void Update()
{
if ((ModuleInterop.Support == null) || (((MonoSupportModule)ModuleInterop.Support).component != this))
return;

isQuitting = false;
SiblingFix();

((MonoSupportModule)ModuleInterop.Support).sceneHandler.OnUpdate();

MelonUnityEvents.OnUpdate.Invoke();
}

void OnDestroy()
{
if ((ModuleInterop.Support == null) || (((MonoSupportModule)ModuleInterop.Support).component != this))
return;

if (!isQuitting)
{
((MonoSupportModule)ModuleInterop.Support).CreateGameObject();
return;
}

OnApplicationDefiniteQuit();
}

void OnApplicationQuit()
{
if ((ModuleInterop.Support == null) || (((MonoSupportModule)ModuleInterop.Support).component != this))
return;

isQuitting = true;
MelonUnityEvents.OnApplicationQuit.Invoke();
}

void OnApplicationDefiniteQuit()
{
MelonUnityEvents.OnApplicationDefiniteQuit.Invoke();
}

void FixedUpdate()
{
if ((ModuleInterop.Support == null) || (((MonoSupportModule)ModuleInterop.Support).component != this))
return;

MelonUnityEvents.OnFixedUpdate.Invoke();
}

void LateUpdate()
{
if ((ModuleInterop.Support == null) || (((MonoSupportModule)ModuleInterop.Support).component != this))
return;

MelonUnityEvents.OnLateUpdate.Invoke();
}

void OnGUI()
{
if ((ModuleInterop.Support == null) || (((MonoSupportModule)ModuleInterop.Support).component != this))
return;

MelonUnityEvents.OnGUI.Invoke();
}
}
}
Loading

0 comments on commit 24f6d7b

Please sign in to comment.