1
+ #if CPP
2
+ using System ;
3
+ using System . Collections ;
4
+ using System . Collections . Generic ;
5
+ using System . Linq ;
6
+ using UnhollowerBaseLib ;
7
+ using UnityEngine ;
8
+
9
+ // CREDIT HerpDerpenstine
10
+ // https://github.com/LavaGang/MelonLoader/blob/master/MelonLoader.Support.Il2Cpp/MelonCoroutines.cs
11
+
12
+ namespace UnityExplorer . Core . Runtime . Il2Cpp
13
+ {
14
+ public static class Il2CppCoroutine
15
+ {
16
+ private struct CoroTuple
17
+ {
18
+ public object WaitCondition ;
19
+ public IEnumerator Coroutine ;
20
+ }
21
+ private static readonly List < CoroTuple > ourCoroutinesStore = new List < CoroTuple > ( ) ;
22
+ private static readonly List < IEnumerator > ourNextFrameCoroutines = new List < IEnumerator > ( ) ;
23
+ private static readonly List < IEnumerator > ourWaitForFixedUpdateCoroutines = new List < IEnumerator > ( ) ;
24
+ private static readonly List < IEnumerator > ourWaitForEndOfFrameCoroutines = new List < IEnumerator > ( ) ;
25
+
26
+ private static readonly List < IEnumerator > tempList = new List < IEnumerator > ( ) ;
27
+
28
+ internal static object Start ( IEnumerator routine )
29
+ {
30
+ if ( routine != null ) ProcessNextOfCoroutine ( routine ) ;
31
+ return routine ;
32
+ }
33
+
34
+ internal static void Stop ( IEnumerator enumerator )
35
+ {
36
+ if ( ourNextFrameCoroutines . Contains ( enumerator ) ) // the coroutine is running itself
37
+ ourNextFrameCoroutines . Remove ( enumerator ) ;
38
+ else
39
+ {
40
+ int coroTupleIndex = ourCoroutinesStore . FindIndex ( c => c . Coroutine == enumerator ) ;
41
+ if ( coroTupleIndex != - 1 ) // the coroutine is waiting for a subroutine
42
+ {
43
+ object waitCondition = ourCoroutinesStore [ coroTupleIndex ] . WaitCondition ;
44
+ if ( waitCondition is IEnumerator waitEnumerator )
45
+ Stop ( waitEnumerator ) ;
46
+
47
+ ourCoroutinesStore . RemoveAt ( coroTupleIndex ) ;
48
+ }
49
+ }
50
+ }
51
+
52
+ private static void ProcessCoroList ( List < IEnumerator > target )
53
+ {
54
+ if ( target . Count == 0 ) return ;
55
+
56
+ // use a temp list to make sure waits made during processing are not handled by same processing invocation
57
+ // additionally, a temp list reduces allocations compared to an array
58
+ tempList . AddRange ( target ) ;
59
+ target . Clear ( ) ;
60
+ foreach ( var enumerator in tempList ) ProcessNextOfCoroutine ( enumerator ) ;
61
+ tempList . Clear ( ) ;
62
+ }
63
+
64
+ internal static void Process ( )
65
+ {
66
+ for ( var i = ourCoroutinesStore . Count - 1 ; i >= 0 ; i -- )
67
+ {
68
+ var tuple = ourCoroutinesStore [ i ] ;
69
+ if ( tuple . WaitCondition is WaitForSeconds waitForSeconds )
70
+ {
71
+ if ( ( waitForSeconds . m_Seconds -= Time . deltaTime ) <= 0 )
72
+ {
73
+ ourCoroutinesStore . RemoveAt ( i ) ;
74
+ ProcessNextOfCoroutine ( tuple . Coroutine ) ;
75
+ }
76
+ }
77
+ }
78
+
79
+ ProcessCoroList ( ourNextFrameCoroutines ) ;
80
+ }
81
+
82
+ internal static void ProcessWaitForFixedUpdate ( ) => ProcessCoroList ( ourWaitForFixedUpdateCoroutines ) ;
83
+
84
+ internal static void ProcessWaitForEndOfFrame ( ) => ProcessCoroList ( ourWaitForEndOfFrameCoroutines ) ;
85
+
86
+ private static void ProcessNextOfCoroutine ( IEnumerator enumerator )
87
+ {
88
+ try
89
+ {
90
+ if ( ! enumerator . MoveNext ( ) ) // Run the next step of the coroutine. If it's done, restore the parent routine
91
+ {
92
+ var indices = ourCoroutinesStore . Select ( ( it , idx ) => ( idx , it ) ) . Where ( it => it . it . WaitCondition == enumerator ) . Select ( it => it . idx ) . ToList ( ) ;
93
+ for ( var i = indices . Count - 1 ; i >= 0 ; i -- )
94
+ {
95
+ var index = indices [ i ] ;
96
+ ourNextFrameCoroutines . Add ( ourCoroutinesStore [ index ] . Coroutine ) ;
97
+ ourCoroutinesStore . RemoveAt ( index ) ;
98
+ }
99
+ return ;
100
+ }
101
+ }
102
+ catch ( Exception e )
103
+ {
104
+ ExplorerCore . LogError ( e . ToString ( ) ) ;
105
+ Stop ( FindOriginalCoro ( enumerator ) ) ; // We want the entire coroutine hierachy to stop when an error happen
106
+ }
107
+
108
+ var next = enumerator . Current ;
109
+ switch ( next )
110
+ {
111
+ case null :
112
+ ourNextFrameCoroutines . Add ( enumerator ) ;
113
+ return ;
114
+ case WaitForFixedUpdate _:
115
+ ourWaitForFixedUpdateCoroutines . Add ( enumerator ) ;
116
+ return ;
117
+ case WaitForEndOfFrame _:
118
+ ourWaitForEndOfFrameCoroutines . Add ( enumerator ) ;
119
+ return ;
120
+ case WaitForSeconds _:
121
+ break ; // do nothing, this one is supported in Process
122
+ case Il2CppObjectBase il2CppObjectBase :
123
+ var nextAsEnumerator = il2CppObjectBase . TryCast < Il2CppSystem . Collections . IEnumerator > ( ) ;
124
+ if ( nextAsEnumerator != null ) // il2cpp IEnumerator also handles CustomYieldInstruction
125
+ next = new Il2CppEnumeratorWrapper ( nextAsEnumerator ) ;
126
+ else
127
+ ExplorerCore . LogWarning ( $ "Unknown coroutine yield object of type { il2CppObjectBase } for coroutine { enumerator } ") ;
128
+ break ;
129
+ }
130
+
131
+ ourCoroutinesStore . Add ( new CoroTuple { WaitCondition = next , Coroutine = enumerator } ) ;
132
+
133
+ if ( next is IEnumerator nextCoro )
134
+ ProcessNextOfCoroutine ( nextCoro ) ;
135
+ }
136
+
137
+ private static IEnumerator FindOriginalCoro ( IEnumerator enumerator )
138
+ {
139
+ int index = ourCoroutinesStore . FindIndex ( ct => ct . WaitCondition == enumerator ) ;
140
+ if ( index == - 1 )
141
+ return enumerator ;
142
+ return FindOriginalCoro ( ourCoroutinesStore [ index ] . Coroutine ) ;
143
+ }
144
+
145
+ private class Il2CppEnumeratorWrapper : IEnumerator
146
+ {
147
+ private readonly Il2CppSystem . Collections . IEnumerator il2cppEnumerator ;
148
+
149
+ public Il2CppEnumeratorWrapper ( Il2CppSystem . Collections . IEnumerator il2CppEnumerator ) => il2cppEnumerator = il2CppEnumerator ;
150
+ public bool MoveNext ( ) => il2cppEnumerator . MoveNext ( ) ;
151
+ public void Reset ( ) => il2cppEnumerator . Reset ( ) ;
152
+ public object Current => il2cppEnumerator . Current ;
153
+ }
154
+ }
155
+ }
156
+ #endif
0 commit comments