Skip to content
This repository was archived by the owner on Feb 28, 2024. It is now read-only.

Commit c5bb9ae

Browse files
authored
Merge pull request #86 from neos-modding-group/logging-fixes
Various Logging Fixes
2 parents 458ba47 + 4f70956 commit c5bb9ae

File tree

5 files changed

+117
-57
lines changed

5 files changed

+117
-57
lines changed

NeosModLoader/AssemblyHider.cs

Lines changed: 86 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,49 @@
33
using HarmonyLib;
44
using System;
55
using System.Collections.Generic;
6+
using System.Diagnostics;
67
using System.Linq;
78
using System.Reflection;
89

910
namespace 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
}

NeosModLoader/Logger.cs

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using BaseX;
22
using System;
3+
using System.Diagnostics;
34

45
namespace NeosModLoader
56
{
@@ -25,7 +26,7 @@ internal static void DebugFuncExternal(Func<object> messageProducer)
2526
{
2627
if (IsDebugEnabled())
2728
{
28-
LogInternal(LogType.DEBUG, messageProducer(), SourceFromStackTrace());
29+
LogInternal(LogType.DEBUG, messageProducer(), SourceFromStackTrace(new(1)));
2930
}
3031
}
3132

@@ -41,27 +42,27 @@ internal static void DebugExternal(object message)
4142
{
4243
if (IsDebugEnabled())
4344
{
44-
LogInternal(LogType.DEBUG, message, SourceFromStackTrace());
45+
LogInternal(LogType.DEBUG, message, SourceFromStackTrace(new(1)));
4546
}
4647
}
4748

4849
internal static void DebugListExternal(object[] messages)
4950
{
5051
if (IsDebugEnabled())
5152
{
52-
LogListInternal(LogType.DEBUG, messages, SourceFromStackTrace());
53+
LogListInternal(LogType.DEBUG, messages, SourceFromStackTrace(new(1)));
5354
}
5455
}
5556

5657
internal static void MsgInternal(string message) => LogInternal(LogType.INFO, message);
57-
internal static void MsgExternal(object message) => LogInternal(LogType.INFO, message, SourceFromStackTrace());
58-
internal static void MsgListExternal(object[] messages) => LogListInternal(LogType.INFO, messages, SourceFromStackTrace());
58+
internal static void MsgExternal(object message) => LogInternal(LogType.INFO, message, SourceFromStackTrace(new(1)));
59+
internal static void MsgListExternal(object[] messages) => LogListInternal(LogType.INFO, messages, SourceFromStackTrace(new(1)));
5960
internal static void WarnInternal(string message) => LogInternal(LogType.WARN, message);
60-
internal static void WarnExternal(object message) => LogInternal(LogType.WARN, message, SourceFromStackTrace());
61-
internal static void WarnListExternal(object[] messages) => LogListInternal(LogType.WARN, messages, SourceFromStackTrace());
61+
internal static void WarnExternal(object message) => LogInternal(LogType.WARN, message, SourceFromStackTrace(new(1)));
62+
internal static void WarnListExternal(object[] messages) => LogListInternal(LogType.WARN, messages, SourceFromStackTrace(new(1)));
6263
internal static void ErrorInternal(string message) => LogInternal(LogType.ERROR, message);
63-
internal static void ErrorExternal(object message) => LogInternal(LogType.ERROR, message, SourceFromStackTrace());
64-
internal static void ErrorListExternal(object[] messages) => LogListInternal(LogType.ERROR, messages, SourceFromStackTrace());
64+
internal static void ErrorExternal(object message) => LogInternal(LogType.ERROR, message, SourceFromStackTrace(new(1)));
65+
internal static void ErrorListExternal(object[] messages) => LogListInternal(LogType.ERROR, messages, SourceFromStackTrace(new(1)));
6566

6667
private static void LogInternal(string logTypePrefix, object message, string? source = null)
6768
{
@@ -94,10 +95,10 @@ private static void LogListInternal(string logTypePrefix, object[] messages, str
9495
}
9596
}
9697

97-
private static string? SourceFromStackTrace()
98+
private static string? SourceFromStackTrace(StackTrace stackTrace)
9899
{
99100
// MsgExternal() and Msg() are above us in the stack
100-
return Util.ExecutingMod(2)?.Name;
101+
return Util.ExecutingMod(stackTrace)?.Name;
101102
}
102103

103104
private sealed class LogType

NeosModLoader/ModConfiguration.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -527,7 +527,7 @@ internal void Save(bool saveDefaultValues = false, bool immediate = false)
527527
{
528528

529529
Thread thread = Thread.CurrentThread;
530-
NeosMod? callee = Util.ExecutingMod();
530+
NeosMod? callee = Util.ExecutingMod(new(1));
531531
Action<bool>? saveAction = null;
532532

533533
// get saved state for this callee

NeosModLoader/NeosVersionReset.cs

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,13 @@ internal static void Initialize()
4040
.Where(IsPostXProcessed)
4141
.ToArray();
4242

43-
string potentialPlugins = postxedAssemblies
44-
.Select(a => Path.GetFileName(a.Location))
45-
.Join(delimiter: ", ");
46-
47-
Logger.DebugFuncInternal(() => $"Found {postxedAssemblies.Length} potential plugins: {potentialPlugins}");
43+
Logger.DebugFuncInternal(() =>
44+
{
45+
string potentialPlugins = postxedAssemblies
46+
.Select(a => Path.GetFileName(a.Location))
47+
.Join(delimiter: ", ");
48+
return $"Found {postxedAssemblies.Length} potential plugins: {potentialPlugins}";
49+
});
4850

4951
HashSet<Assembly> expectedPostXAssemblies = GetExpectedPostXAssemblies();
5052

@@ -68,8 +70,12 @@ internal static void Initialize()
6870
})
6971
.ToArray();
7072

71-
string actualPlugins = plugins.Keys.Join(delimiter: ", ");
72-
Logger.DebugFuncInternal(() => $"Found {plugins.Count} actual plugins: {actualPlugins}");
73+
74+
Logger.DebugFuncInternal(() =>
75+
{
76+
string actualPlugins = plugins.Keys.Join(delimiter: ", ");
77+
return $"Found {plugins.Count} actual plugins: {actualPlugins}";
78+
});
7379

7480
// warn about the assemblies we couldn't map to plugins
7581
foreach (Assembly assembly in unmatchedAssemblies)

NeosModLoader/Util.cs

Lines changed: 5 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -13,17 +13,13 @@ namespace NeosModLoader
1313
internal static class Util
1414
{
1515
/// <summary>
16-
/// Get the executing mod by stack trace analysis. Always skips the first two frames, being this method and you, the caller.
16+
/// Get the executing mod by stack trace analysis.
1717
/// You may skip extra frames if you know your callers are guaranteed to be NML code.
1818
/// </summary>
19-
/// <param name="nmlCalleeDepth">The number NML method calls above you in the stack</param>
19+
/// <param name="stackTrace">A stack trace captured by the callee</param>
2020
/// <returns>The executing mod, or null if none found</returns>
21-
internal static NeosMod? ExecutingMod(int nmlCalleeDepth = 0)
21+
internal static NeosMod? ExecutingMod(StackTrace stackTrace)
2222
{
23-
// example: ExecutingMod(), SourceFromStackTrace(), MsgExternal(), Msg(), ACTUAL MOD CODE
24-
// you'd skip 4 frames
25-
// we always skip ExecutingMod() and whoever called us (as this is an internal method), which is where the 2 comes from
26-
StackTrace stackTrace = new(2 + nmlCalleeDepth);
2723
for (int i = 0; i < stackTrace.FrameCount; i++)
2824
{
2925
Assembly? assembly = stackTrace.GetFrame(i)?.GetMethod()?.DeclaringType?.Assembly;
@@ -35,26 +31,6 @@ internal static class Util
3531
return null;
3632
}
3733

38-
/// <summary>
39-
/// Get the calling assembly by stack trace analysis. Always skips the first one frame, being this method and you, the caller.
40-
/// </summary>
41-
/// <param name="skipFrames">The number of extra frame skip in the stack</param>
42-
/// <returns>The executing mod, or null if none found</returns>
43-
internal static Assembly? GetCallingAssembly(int skipFrames = 0)
44-
{
45-
// same logic as ExecutingMod(), but simpler case
46-
StackTrace stackTrace = new(2 + skipFrames);
47-
for (int i = 0; i < stackTrace.FrameCount; i++)
48-
{
49-
Assembly? assembly = stackTrace.GetFrame(i)?.GetMethod()?.DeclaringType?.Assembly;
50-
if (assembly != null)
51-
{
52-
return assembly;
53-
}
54-
}
55-
return null;
56-
}
57-
5834
/// <summary>
5935
/// Used to debounce a method call. The underlying method will be called after there have been no additional calls
6036
/// for n milliseconds.
@@ -142,6 +118,7 @@ private static bool CheckType(Type type, Predicate<Type> predicate)
142118
{
143119
return false;
144120
}
121+
145122
try
146123
{
147124
string _name = type.Name;
@@ -158,7 +135,7 @@ private static bool CheckType(Type type, Predicate<Type> predicate)
158135
}
159136
catch (Exception e)
160137
{
161-
Logger.DebugFuncInternal(() => $"Could not load type \"{type.Name}\": {e}");
138+
Logger.DebugFuncInternal(() => $"Could not load type \"{type}\": {e}");
162139
return false;
163140
}
164141
}

0 commit comments

Comments
 (0)