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

Hide modding-related types from TypeHelper.FindType #67

Merged
merged 5 commits into from
Aug 29, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Member

@EIA485 EIA485 Aug 22, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably add a comment here clarifying why a specific case is needed.
afak in theory, if nml is the only thing a user is using that loads harmony, and the user followed the install instructions of placing harmony in nml_libs, initialAssemblies shouldn't contain harmony.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment added in f9c6f22. The install instructions weren't always to use nml_libs, and I don't want to punish users who followed the old steps.


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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wont any plugin loaded after nml is loaded cause this?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, plugins are loaded before NML starts executing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could add another config option to allow late loaded types thru our filter.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's doable. I'm just not sure who'd be late loading types at all, hence the warning log.

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;
}
}
10 changes: 8 additions & 2 deletions NeosModLoader/Util.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;
using System.Security.Cryptography;

namespace NeosModLoader
{
Expand Down Expand Up @@ -84,5 +85,10 @@ internal static string GenerateSHA256(string filepath)
return BitConverter.ToString(hash).Replace("-", "");
}

internal static HashSet<T> ToHashSet<T>(this IEnumerable<T> source, IEqualityComparer<T>? comparer = null)
{
return new HashSet<T>(source, comparer);
}

}
}
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. |