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

Commit

Permalink
Hide modding-related types from TypeHelper.FindType (#67)
Browse files Browse the repository at this point in the history
* Fix #64

* Add a debug log

* Add a configuration to disable this feature

* Document why I remove NML and harmony
  • Loading branch information
zkxs authored Aug 29, 2022
1 parent f7b9ae2 commit 635592b
Show file tree
Hide file tree
Showing 6 changed files with 107 additions and 3 deletions.
76 changes: 76 additions & 0 deletions NeosModLoader/AssemblyHider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
using BaseX;
using HarmonyLib;
using System;
using System.Collections.Generic;
using System.Reflection;

namespace NeosModLoader
{
internal static class AssemblyHider
{
private static HashSet<Assembly>? neosAssemblies;
private static HashSet<Assembly>? modAssemblies;

/// <summary>
/// Patch Neos's type lookup code to not see mod-related types. This is needed, because users can pass
/// arbitrary strings to TypeHelper.FindType(), which can be used to detect if someone is running mods.
/// </summary>
/// <param name="harmony">Our NML harmony instance</param>
/// <param name="initialAssemblies">Assemblies that were loaded when NML first started</param>
internal static void PatchNeos(Harmony harmony, HashSet<Assembly> initialAssemblies)
{
if (ModLoaderConfiguration.Get().HideModTypes)
{
neosAssemblies = GetNeosAssemblies(initialAssemblies);
modAssemblies = GetModAssemblies();
MethodInfo target = AccessTools.DeclaredMethod(typeof(TypeHelper), nameof(TypeHelper.FindType));
MethodInfo patch = AccessTools.DeclaredMethod(typeof(AssemblyHider), nameof(FindTypePostfix));
harmony.Patch(target, postfix: new HarmonyMethod(patch));
}
}

private static HashSet<Assembly> GetNeosAssemblies(HashSet<Assembly> initialAssemblies)
{
// Remove NML itself, as its types should be hidden but it's guaranteed to be loaded.
initialAssemblies.Remove(Assembly.GetExecutingAssembly());

// Remove Harmony, as users who aren't using nml_libs will already have it loaded.
initialAssemblies.Remove(typeof(Harmony).Assembly);

return initialAssemblies;
}

private static HashSet<Assembly> GetModAssemblies()
{
// start with ALL assemblies
HashSet<Assembly> assemblies = AppDomain.CurrentDomain.GetAssemblies().ToHashSet();

// remove assemblies that already existed before NML loaded
assemblies.ExceptWith(neosAssemblies);

return assemblies;
}

private static void FindTypePostfix(ref Type? __result)
{
if (__result != null && !neosAssemblies!.Contains(__result.Assembly))
{
if (!modAssemblies!.Contains(__result.Assembly))
{
// an assembly was in neither neosAssemblies nor modAssemblies
// this implies someone late-loaded an assembly after NML, and it was later used in-game
// this is super weird, and probably shouldn't ever happen... but if it does, I want to know about it.
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.");
}
else
{
Type type = __result;
Logger.DebugFuncInternal(() => $"Hid type \"{type}\" from Neos");
}

// Pretend the type doesn't exist
__result = null;
}
}
}
}
7 changes: 6 additions & 1 deletion NeosModLoader/ExecutionHook.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using FrooxEngine;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;

namespace NeosModLoader
{
Expand All @@ -17,8 +19,11 @@ static ExecutionHook()
{
try
{
HashSet<Assembly> initialAssemblies = AppDomain.CurrentDomain.GetAssemblies().ToHashSet();
SplashChanger.SetCustom("Loading libraries");
AssemblyFile[] loadedAssemblies = AssemblyLoader.LoadAssembliesFromDir("nml_libs");
// note that harmony may not be loaded until this point, so this class cannot directly inport HarmonyLib.

if (loadedAssemblies.Length != 0)
{
string loadedAssemblyList = string.Join("\n", loadedAssemblies.Select(a => a.Assembly.FullName + " Sha256=" + a.Sha256));
Expand All @@ -28,7 +33,7 @@ static ExecutionHook()
SplashChanger.SetCustom("Initializing");
DebugInfo.Log();
NeosVersionReset.Initialize();
ModLoader.LoadMods();
HarmonyWorker.LoadModsAndHideModAssemblies(initialAssemblies);
SplashChanger.SetCustom("Loaded");
}
catch (Exception e) // it's important that this doesn't send exceptions back to Neos
Expand Down
18 changes: 18 additions & 0 deletions NeosModLoader/HarmonyWorker.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using HarmonyLib;
using System.Collections.Generic;
using System.Reflection;

namespace NeosModLoader
{
// this class does all the harmony-related NML work.
// this is needed to avoid importing harmony in ExecutionHook, where it may not be loaded yet.
internal class HarmonyWorker
{
internal static void LoadModsAndHideModAssemblies(HashSet<Assembly> initialAssemblies)
{
Harmony harmony = new("com.neosmodloader");
ModLoader.LoadMods(harmony);
AssemblyHider.PatchNeos(harmony, initialAssemblies);
}
}
}
3 changes: 1 addition & 2 deletions NeosModLoader/ModLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static IEnumerable<NeosModBase> Mods()
.ToList();
}

internal static void LoadMods()
internal static void LoadMods(Harmony harmony)
{
ModLoaderConfiguration config = ModLoaderConfiguration.Get();
if (config.NoMods)
Expand Down Expand Up @@ -92,7 +92,6 @@ internal static void LoadMods()
}

SplashChanger.SetCustom("Hooking big fish");
Harmony harmony = new("net.michaelripley.neosmodloader");
ModConfiguration.RegisterShutdownHook(harmony);

foreach (LoadedNeosMod mod in LoadedMods)
Expand Down
5 changes: 5 additions & 0 deletions NeosModLoader/ModLoaderConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ internal static ModLoaderConfiguration Get()
{
_configuration.LogConflicts = false;
}
else if ("hidemodtypes".Equals(key) && "false".Equals(value))
{
_configuration.HideModTypes = false;
}
}
}
}
Expand Down Expand Up @@ -96,5 +100,6 @@ private static string GetAssemblyDirectory()
public bool NoLibraries { get; private set; } = false;
public bool AdvertiseVersion { get; private set; } = false;
public bool LogConflicts { get; private set; } = true;
public bool HideModTypes { get; private set; } = true;
}
}
1 change: 1 addition & 0 deletions doc/modloader_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ Not all keys are required to be present. Missing keys will use the defaults outl
| `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. |
| `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. |
| `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. |
| `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 comments on commit 635592b

Please sign in to comment.