33using HarmonyLib ;
44using System ;
55using System . Collections . Generic ;
6+ using System . Diagnostics ;
67using System . Linq ;
78using System . Reflection ;
89
910namespace NeosModLoader
1011{
1112 internal static class AssemblyHider
1213 {
14+ /// <summary>
15+ /// Companies that indicate an assembly is part of .NET.
16+ /// This list was found by debug logging the AssemblyCompanyAttribute for all loaded assemblies.
17+ /// </summary>
18+ private static HashSet < string > knownDotNetCompanies = new List < string > ( )
19+ {
20+ "Mono development team" , // used by .NET stuff and Mono.Security
21+ } . Select ( company => company . ToLower ( ) ) . ToHashSet ( ) ;
22+
23+ /// <summary>
24+ /// Products that indicate an assembly is part of .NET.
25+ /// This list was found by debug logging the AssemblyProductAttribute for all loaded assemblies.
26+ /// </summary>
27+ private static HashSet < string > knownDotNetProducts = new List < string > ( )
28+ {
29+ "Microsoft® .NET" , // used by a few System.* assemblies
30+ "Microsoft® .NET Framework" , // used by most of the System.* assemblies
31+ "Mono Common Language Infrastructure" , // used by mscorlib stuff
32+ } . Select ( product => product . ToLower ( ) ) . ToHashSet ( ) ;
33+
34+ /// <summary>
35+ /// Assemblies that were already loaded when NML started up, minus a couple known non-Neos assemblies.
36+ /// </summary>
1337 private static HashSet < Assembly > ? neosAssemblies ;
38+
39+ /// <summary>
40+ /// Assemblies that 100% exist due to a mod
41+ /// </summary>
1442 private static HashSet < Assembly > ? modAssemblies ;
1543
44+ /// <summary>
45+ /// .NET assembiles we want to ignore in some cases, like the callee check for the AppDomain.GetAssemblies() patch
46+ /// </summary>
47+ private static HashSet < Assembly > ? dotNetAssemblies ;
48+
1649 /// <summary>
1750 /// Patch Neos's type lookup code to not see mod-related types. This is needed, because users can pass
1851 /// arbitrary strings to TypeHelper.FindType(), which can be used to detect if someone is running mods.
@@ -23,26 +56,28 @@ internal static void PatchNeos(Harmony harmony, HashSet<Assembly> initialAssembl
2356 {
2457 if ( ModLoaderConfiguration . Get ( ) . HideModTypes )
2558 {
59+ // initialize the static assembly sets that our patches will need later
2660 neosAssemblies = GetNeosAssemblies ( initialAssemblies ) ;
27- modAssemblies = GetModAssemblies ( ) ;
61+ modAssemblies = GetModAssemblies ( neosAssemblies ) ;
62+ dotNetAssemblies = neosAssemblies . Where ( LooksLikeDotNetAssembly ) . ToHashSet ( ) ;
2863
2964 // TypeHelper.FindType explicitly does a type search
30- MethodInfo findTypeTarget = AccessTools . DeclaredMethod ( typeof ( TypeHelper ) , nameof ( TypeHelper . FindType ) ) ;
65+ MethodInfo findTypeTarget = AccessTools . DeclaredMethod ( typeof ( TypeHelper ) , nameof ( TypeHelper . FindType ) , new Type [ ] { typeof ( string ) } ) ;
3166 MethodInfo findTypePatch = AccessTools . DeclaredMethod ( typeof ( AssemblyHider ) , nameof ( FindTypePostfix ) ) ;
3267 harmony . Patch ( findTypeTarget , postfix : new HarmonyMethod ( findTypePatch ) ) ;
3368
3469 // WorkerManager.IsValidGenericType checks a type for validity, and if it returns `true` it reveals that the type exists
35- MethodInfo isValidGenericTypeTarget = AccessTools . DeclaredMethod ( typeof ( WorkerManager ) , nameof ( WorkerManager . IsValidGenericType ) ) ;
70+ MethodInfo isValidGenericTypeTarget = AccessTools . DeclaredMethod ( typeof ( WorkerManager ) , nameof ( WorkerManager . IsValidGenericType ) , new Type [ ] { typeof ( Type ) , typeof ( bool ) } ) ;
3671 MethodInfo isValidGenericTypePatch = AccessTools . DeclaredMethod ( typeof ( AssemblyHider ) , nameof ( IsValidTypePostfix ) ) ;
3772 harmony . Patch ( isValidGenericTypeTarget , postfix : new HarmonyMethod ( isValidGenericTypePatch ) ) ;
3873
3974 // WorkerManager.GetType uses FindType, but upon failure fails back to doing a (strangely) exhausitive reflection-based search for the type
40- MethodInfo getTypeTarget = AccessTools . DeclaredMethod ( typeof ( WorkerManager ) , nameof ( WorkerManager . GetType ) ) ;
75+ MethodInfo getTypeTarget = AccessTools . DeclaredMethod ( typeof ( WorkerManager ) , nameof ( WorkerManager . GetType ) , new Type [ ] { typeof ( string ) } ) ;
4176 MethodInfo getTypePatch = AccessTools . DeclaredMethod ( typeof ( AssemblyHider ) , nameof ( FindTypePostfix ) ) ;
4277 harmony . Patch ( getTypeTarget , postfix : new HarmonyMethod ( getTypePatch ) ) ;
4378
4479 // FrooxEngine likes to enumerate all types in all assemblies, which is prone to issues (such as crashing FrooxCode if a type isn't loadable)
45- MethodInfo getAssembliesTarget = AccessTools . DeclaredMethod ( typeof ( AppDomain ) , nameof ( AppDomain . GetAssemblies ) ) ;
80+ MethodInfo getAssembliesTarget = AccessTools . DeclaredMethod ( typeof ( AppDomain ) , nameof ( AppDomain . GetAssemblies ) , new Type [ ] { } ) ;
4681 MethodInfo getAssembliesPatch = AccessTools . DeclaredMethod ( typeof ( AssemblyHider ) , nameof ( GetAssembliesPostfix ) ) ;
4782 harmony . Patch ( getAssembliesTarget , postfix : new HarmonyMethod ( getAssembliesPatch ) ) ;
4883 }
@@ -59,14 +94,15 @@ private static HashSet<Assembly> GetNeosAssemblies(HashSet<Assembly> initialAsse
5994 return initialAssemblies ;
6095 }
6196
62- private static HashSet < Assembly > GetModAssemblies ( )
97+ private static HashSet < Assembly > GetModAssemblies ( HashSet < Assembly > neosAssemblies )
6398 {
6499 // start with ALL assemblies
65100 HashSet < Assembly > assemblies = AppDomain . CurrentDomain . GetAssemblies ( ) . ToHashSet ( ) ;
66101
67- // remove assemblies that already existed before NML loaded
102+ // remove assemblies that we know to have come with Neos
68103 assemblies . ExceptWith ( neosAssemblies ) ;
69104
105+ // what's left are assemblies that magically appeared during the mod loading process. So mods and their dependencies.
70106 return assemblies ;
71107 }
72108
@@ -93,7 +129,7 @@ private static bool IsModAssembly(Assembly assembly, string typeOrAssembly, stri
93129 // known type from a mod assembly
94130 if ( log )
95131 {
96- Logger . DebugInternal ( $ "Hid { typeOrAssembly } \" { name } \" from Neos") ;
132+ Logger . DebugFuncInternal ( ( ) => $ "Hid { typeOrAssembly } \" { name } \" from Neos") ;
97133 }
98134 return true ; // hide the thing
99135 }
@@ -165,15 +201,55 @@ private static void IsValidTypePostfix(ref bool __result, Type type)
165201
166202 private static void GetAssembliesPostfix ( ref Assembly [ ] __result )
167203 {
168- Assembly ? callingAssembly = Util . GetCallingAssembly ( ) ;
204+ Assembly ? callingAssembly = GetCallingAssembly ( new ( 1 ) ) ;
169205 if ( callingAssembly != null && neosAssemblies ! . Contains ( callingAssembly ) )
170206 {
171- // if we're being called by Neos, then hide mod assemblies
207+ // if we're being called by Neos code , then hide mod assemblies
172208 Logger . DebugFuncInternal ( ( ) => $ "Intercepting call to AppDomain.GetAssemblies() from { callingAssembly } ") ;
173209 __result = __result
174210 . Where ( assembly => ! IsModAssembly ( assembly , forceShowLate : true ) ) // it turns out Neos itself late-loads a bunch of stuff, so we force-show late-loaded assemblies here
175211 . ToArray ( ) ;
176212 }
177213 }
214+
215+ /// <summary>
216+ /// Get the calling assembly by stack trace analysis, ignoring .NET assemblies.
217+ /// This implementation is SPECIFICALLY for the AppDomain.GetAssemblies() patch and may not be valid for other use-cases.
218+ /// </summary>
219+ /// <param name="stacktrace">A stack trace captured by the callee</param>
220+ /// <returns>The executing assembly, or null if none found</returns>
221+ private static Assembly ? GetCallingAssembly ( StackTrace stackTrace )
222+ {
223+ for ( int i = 0 ; i < stackTrace . FrameCount ; i ++ )
224+ {
225+ Assembly ? assembly = stackTrace . GetFrame ( i ) ? . GetMethod ( ) ? . DeclaringType ? . Assembly ;
226+ // .NET calls AppDomain.GetAssemblies() a bunch internally, and we don't want to intercept those calls UNLESS they originated from Neos code.
227+ if ( assembly != null && ! dotNetAssemblies ! . Contains ( assembly ) )
228+ {
229+ return assembly ;
230+ }
231+ }
232+ return null ;
233+ }
234+
235+ private static bool LooksLikeDotNetAssembly ( Assembly assembly )
236+ {
237+ // check the assembly's company
238+ string ? company = assembly . GetCustomAttribute < AssemblyCompanyAttribute > ( ) ? . Company ;
239+ if ( company != null && knownDotNetCompanies . Contains ( company . ToLower ( ) ) )
240+ {
241+ return true ;
242+ }
243+
244+ // check the assembly's product
245+ string ? product = assembly . GetCustomAttribute < AssemblyProductAttribute > ( ) ? . Product ;
246+ if ( product != null && knownDotNetProducts . Contains ( product . ToLower ( ) ) )
247+ {
248+ return true ;
249+ }
250+
251+ // nothing matched, this is probably not part of .NET
252+ return false ;
253+ }
178254 }
179255}
0 commit comments