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

Commit 635592b

Browse files
authored
Hide modding-related types from TypeHelper.FindType (#67)
* Fix #64 * Add a debug log * Add a configuration to disable this feature * Document why I remove NML and harmony
1 parent f7b9ae2 commit 635592b

File tree

6 files changed

+107
-3
lines changed

6 files changed

+107
-3
lines changed

NeosModLoader/AssemblyHider.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
using BaseX;
2+
using HarmonyLib;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Reflection;
6+
7+
namespace NeosModLoader
8+
{
9+
internal static class AssemblyHider
10+
{
11+
private static HashSet<Assembly>? neosAssemblies;
12+
private static HashSet<Assembly>? modAssemblies;
13+
14+
/// <summary>
15+
/// Patch Neos's type lookup code to not see mod-related types. This is needed, because users can pass
16+
/// arbitrary strings to TypeHelper.FindType(), which can be used to detect if someone is running mods.
17+
/// </summary>
18+
/// <param name="harmony">Our NML harmony instance</param>
19+
/// <param name="initialAssemblies">Assemblies that were loaded when NML first started</param>
20+
internal static void PatchNeos(Harmony harmony, HashSet<Assembly> initialAssemblies)
21+
{
22+
if (ModLoaderConfiguration.Get().HideModTypes)
23+
{
24+
neosAssemblies = GetNeosAssemblies(initialAssemblies);
25+
modAssemblies = GetModAssemblies();
26+
MethodInfo target = AccessTools.DeclaredMethod(typeof(TypeHelper), nameof(TypeHelper.FindType));
27+
MethodInfo patch = AccessTools.DeclaredMethod(typeof(AssemblyHider), nameof(FindTypePostfix));
28+
harmony.Patch(target, postfix: new HarmonyMethod(patch));
29+
}
30+
}
31+
32+
private static HashSet<Assembly> GetNeosAssemblies(HashSet<Assembly> initialAssemblies)
33+
{
34+
// Remove NML itself, as its types should be hidden but it's guaranteed to be loaded.
35+
initialAssemblies.Remove(Assembly.GetExecutingAssembly());
36+
37+
// Remove Harmony, as users who aren't using nml_libs will already have it loaded.
38+
initialAssemblies.Remove(typeof(Harmony).Assembly);
39+
40+
return initialAssemblies;
41+
}
42+
43+
private static HashSet<Assembly> GetModAssemblies()
44+
{
45+
// start with ALL assemblies
46+
HashSet<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies().ToHashSet();
47+
48+
// remove assemblies that already existed before NML loaded
49+
assemblies.ExceptWith(neosAssemblies);
50+
51+
return assemblies;
52+
}
53+
54+
private static void FindTypePostfix(ref Type? __result)
55+
{
56+
if (__result != null && !neosAssemblies!.Contains(__result.Assembly))
57+
{
58+
if (!modAssemblies!.Contains(__result.Assembly))
59+
{
60+
// an assembly was in neither neosAssemblies nor modAssemblies
61+
// this implies someone late-loaded an assembly after NML, and it was later used in-game
62+
// this is super weird, and probably shouldn't ever happen... but if it does, I want to know about it.
63+
Logger.WarnInternal($"The \"{__result}\" type does not appear to part of Neos or a mod. It is unclear whether it should be hidden or not.");
64+
}
65+
else
66+
{
67+
Type type = __result;
68+
Logger.DebugFuncInternal(() => $"Hid type \"{type}\" from Neos");
69+
}
70+
71+
// Pretend the type doesn't exist
72+
__result = null;
73+
}
74+
}
75+
}
76+
}

NeosModLoader/ExecutionHook.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using FrooxEngine;
22
using System;
3+
using System.Collections.Generic;
34
using System.Linq;
5+
using System.Reflection;
46

57
namespace NeosModLoader
68
{
@@ -17,8 +19,11 @@ static ExecutionHook()
1719
{
1820
try
1921
{
22+
HashSet<Assembly> initialAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToHashSet();
2023
SplashChanger.SetCustom("Loading libraries");
2124
AssemblyFile[] loadedAssemblies = AssemblyLoader.LoadAssembliesFromDir("nml_libs");
25+
// note that harmony may not be loaded until this point, so this class cannot directly inport HarmonyLib.
26+
2227
if (loadedAssemblies.Length != 0)
2328
{
2429
string loadedAssemblyList = string.Join("\n", loadedAssemblies.Select(a => a.Assembly.FullName + " Sha256=" + a.Sha256));
@@ -28,7 +33,7 @@ static ExecutionHook()
2833
SplashChanger.SetCustom("Initializing");
2934
DebugInfo.Log();
3035
NeosVersionReset.Initialize();
31-
ModLoader.LoadMods();
36+
HarmonyWorker.LoadModsAndHideModAssemblies(initialAssemblies);
3237
SplashChanger.SetCustom("Loaded");
3338
}
3439
catch (Exception e) // it's important that this doesn't send exceptions back to Neos

NeosModLoader/HarmonyWorker.cs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using HarmonyLib;
2+
using System.Collections.Generic;
3+
using System.Reflection;
4+
5+
namespace NeosModLoader
6+
{
7+
// this class does all the harmony-related NML work.
8+
// this is needed to avoid importing harmony in ExecutionHook, where it may not be loaded yet.
9+
internal class HarmonyWorker
10+
{
11+
internal static void LoadModsAndHideModAssemblies(HashSet<Assembly> initialAssemblies)
12+
{
13+
Harmony harmony = new("com.neosmodloader");
14+
ModLoader.LoadMods(harmony);
15+
AssemblyHider.PatchNeos(harmony, initialAssemblies);
16+
}
17+
}
18+
}

NeosModLoader/ModLoader.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public static IEnumerable<NeosModBase> Mods()
3131
.ToList();
3232
}
3333

34-
internal static void LoadMods()
34+
internal static void LoadMods(Harmony harmony)
3535
{
3636
ModLoaderConfiguration config = ModLoaderConfiguration.Get();
3737
if (config.NoMods)
@@ -92,7 +92,6 @@ internal static void LoadMods()
9292
}
9393

9494
SplashChanger.SetCustom("Hooking big fish");
95-
Harmony harmony = new("net.michaelripley.neosmodloader");
9695
ModConfiguration.RegisterShutdownHook(harmony);
9796

9897
foreach (LoadedNeosMod mod in LoadedMods)

NeosModLoader/ModLoaderConfiguration.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ internal static ModLoaderConfiguration Get()
5959
{
6060
_configuration.LogConflicts = false;
6161
}
62+
else if ("hidemodtypes".Equals(key) && "false".Equals(value))
63+
{
64+
_configuration.HideModTypes = false;
65+
}
6266
}
6367
}
6468
}
@@ -96,5 +100,6 @@ private static string GetAssemblyDirectory()
96100
public bool NoLibraries { get; private set; } = false;
97101
public bool AdvertiseVersion { get; private set; } = false;
98102
public bool LogConflicts { get; private set; } = true;
103+
public bool HideModTypes { get; private set; } = true;
99104
}
100105
}

doc/modloader_config.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,4 @@ Not all keys are required to be present. Missing keys will use the defaults outl
1818
| `advertiseversion` | `false` | If `false`, your version will be spoofed and will resemble `2021.8.29.1240`. If `true`, your version will be left unaltered and will resemble `2021.8.29.1240+NeosModLoader.dll`. This version string is visible to other players under certain circumstances. |
1919
| `unsafe` | `false` | If `true`, the version spoofing safety check is disabled and it will still work even if you have other Neos plugins. DO NOT load plugin components in multiplayer sessions, as it will break things and cause crashes. Plugin components should only be used in your local home or user space. |
2020
| `logconflicts` | `true` | If `false`, conflict logging will be disabled. If `true`, potential mod conflicts will be logged. If `debug` is also `true` this will be more verbose. |
21+
| `hidemodtypes` | `true` | If `true`, mod-related types will be hidden in-game. If `false`, no types will be hidden, which makes NML detectable in-game. |

0 commit comments

Comments
 (0)