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

Allow loading libraries from nml_libs folder #19

Merged
merged 12 commits into from
Apr 8, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

namespace NeosModLoader
{
internal class ModAssembly
internal class AssemblyFile
{
internal string File { get; }
internal Assembly Assembly { get; set; }
internal ModAssembly(string file)
internal AssemblyFile(string file)
{
File = file;
}
Expand Down
89 changes: 89 additions & 0 deletions NeosModLoader/AssemblyLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;

namespace NeosModLoader
{
internal static class AssemblyLoader
{
private static AssemblyFile[] GetAssembliesFromDir(string dirName)
{
string assembliesDirectory = Path.Combine(Directory.GetCurrentDirectory(), dirName);

Logger.MsgInternal($"loading assemblies from {dirName}");

AssemblyFile[] assembliesToLoad = null;
try
{
assembliesToLoad = Directory.GetFiles(assembliesDirectory, "*.dll")
.Select(file => new AssemblyFile(file))
.ToArray();

Array.Sort(assembliesToLoad, (a, b) => string.CompareOrdinal(a.File, b.File));
}
catch (Exception e)
{
if (e is DirectoryNotFoundException)
{
Logger.MsgInternal($"{dirName} directory not found, creating it now.");
try
{
Directory.CreateDirectory(assembliesDirectory);
}
catch (Exception e2)
{
Logger.ErrorInternal($"Error creating ${dirName} directory:\n{e2}");
}
}
else
{
Logger.ErrorInternal($"Error enumerating ${dirName} directory:\n{e}");
}
}
return assembliesToLoad;
}

private static void LoadAssembly(AssemblyFile assemblyFile)
{
string filename = Path.GetFileName(assemblyFile.File);
SplashChanger.SetCustom($"Loading file: {filename}");
Assembly assembly;
try
{
Logger.DebugInternal($"load assembly {filename}");
assembly = Assembly.LoadFile(assemblyFile.File);
}
catch (Exception e)
{
Logger.ErrorInternal($"error loading assembly from {assemblyFile.File}: {e}");
return;
}
if (assembly == null)
{
Logger.ErrorInternal($"unexpected null loading assembly from {assemblyFile.File}");
return;
}
assemblyFile.Assembly = assembly;
}

internal static AssemblyFile[] LoadAssembliesFromDir(string dirName) {
var assembliesOrNull = GetAssembliesFromDir(dirName);
if (assembliesOrNull is AssemblyFile[] assembliesToLoad) {
foreach (AssemblyFile assemblyFile in assembliesToLoad)
{
try
{
LoadAssembly(assemblyFile);
}
catch (Exception e)
{
Logger.ErrorInternal($"Unexpected exception loading assembly from {assemblyFile.File}:\n{e}");
}
}
}

return assembliesOrNull;
}
}
}
2 changes: 2 additions & 0 deletions NeosModLoader/ExecutionHook.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ static ExecutionHook()
{
try
{
SplashChanger.SetCustom("Loading libraries");
AssemblyLoader.LoadAssembliesFromDir("nml_libs");
SplashChanger.SetCustom("Initializing");
DebugInfo.Log();
NeosVersionReset.Initialize();
Expand Down
4 changes: 2 additions & 2 deletions NeosModLoader/LoadedNeosMod.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
{
internal class LoadedNeosMod
{
internal LoadedNeosMod(NeosMod neosMod, ModAssembly modAssembly)
internal LoadedNeosMod(NeosMod neosMod, AssemblyFile modAssembly)
{
NeosMod = neosMod;
ModAssembly = modAssembly;
}

internal NeosMod NeosMod { get; private set; }
internal ModAssembly ModAssembly { get; private set; }
internal AssemblyFile ModAssembly { get; private set; }
internal ModConfiguration ModConfiguration { get; set; }
internal bool AllowSavingConfiguration = true;
internal bool FinishedLoading { get => NeosMod.FinishedLoading; set => NeosMod.FinishedLoading = value; }
Expand Down
74 changes: 6 additions & 68 deletions NeosModLoader/ModLoader.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,58 +39,18 @@ internal static void LoadMods()
}
SplashChanger.SetCustom("Looking for mods");

string modDirectory = Path.Combine(Directory.GetCurrentDirectory(), "nml_mods");

Logger.MsgInternal($"loading mods from {modDirectory}");

// generate list of assemblies to load
ModAssembly[] modsToLoad = null;
try
{
modsToLoad = Directory.GetFiles(modDirectory, "*.dll")
.Select(file => new ModAssembly(file))
.ToArray();

Array.Sort(modsToLoad, (a, b) => string.CompareOrdinal(a.File, b.File));
}
catch (Exception e)
{
if (e is DirectoryNotFoundException)
{
Logger.MsgInternal("mod directory not found, creating it now.");
try
{
Directory.CreateDirectory(modDirectory);
}
catch (Exception e2)
{
Logger.ErrorInternal($"Error creating mod directory:\n{e2}");
}
}
else
{
Logger.ErrorInternal($"Error enumerating mod directory:\n{e}");
}
AssemblyFile[] modsToLoad;
if (AssemblyLoader.LoadAssembliesFromDir("nml_mods") is AssemblyFile[] arr) {
modsToLoad = arr;
} else {
return;
}

ModConfiguration.EnsureDirectoryExists();

// mods assemblies are all loaded before hooking begins so mods can interconnect if needed
foreach (ModAssembly mod in modsToLoad)
{
try
{
LoadAssembly(mod);
}
catch (Exception e)
{
Logger.ErrorInternal($"Unexpected exception loading mod assembly from {mod.File}:\n{e}");
}
}

// call Initialize() each mod
foreach (ModAssembly mod in modsToLoad)
foreach (AssemblyFile mod in modsToLoad)
{
try
{
Expand Down Expand Up @@ -183,30 +143,8 @@ private static string TypesForOwner(Patches patches, string owner)
return $"prefix={prefixCount}; postfix={postfixCount}; transpiler={transpilerCount}; finalizer={finalizerCount}";
}

private static void LoadAssembly(ModAssembly mod)
{
SplashChanger.SetCustom($"Loading file: {mod.File}");
Assembly assembly;
try
{
Logger.DebugInternal($"Loading assembly [{Path.GetFileName(mod.File)}]");
assembly = Assembly.LoadFile(mod.File);
}
catch (Exception e)
{
Logger.ErrorInternal($"error loading assembly from {mod.File}: {e}");
return;
}
if (assembly == null)
{
Logger.ErrorInternal($"unexpected null loading assembly from {mod.File}");
return;
}
mod.Assembly = assembly;
}

// loads mod class and mod config
private static LoadedNeosMod InitializeMod(ModAssembly mod)
private static LoadedNeosMod InitializeMod(AssemblyFile mod)
{
if (mod.Assembly == null)
{
Expand Down
5 changes: 5 additions & 0 deletions NeosModLoader/ModLoaderConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ internal static ModLoaderConfiguration Get()
{
_configuration.NoMods = true;
}
else if ("nolibraries".Equals(key) && "true".Equals(value))
{
_configuration.NoLibraries = true;
}
else if ("advertiseversion".Equals(key) && "true".Equals(value))
{
_configuration.AdvertiseVersion = true;
Expand Down Expand Up @@ -89,6 +93,7 @@ private static string GetAssemblyDirectory()
public bool Debug { get; private set; } = false;
public bool HideVisuals { get; private set; } = false;
public bool NoMods { get; private set; } = false;
public bool NoLibraries { get; private set; } = false;
public bool AdvertiseVersion { get; private set; } = false;
public bool LogConflicts { get; private set; } = true;
}
Expand Down
48 changes: 41 additions & 7 deletions NeosModLoader/SplashChanger.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
using FrooxEngine;
using HarmonyLib;
using System;
using System.Reflection;

namespace NeosModLoader
{
Expand All @@ -9,6 +9,44 @@ namespace NeosModLoader
internal static class SplashChanger
{
private static bool failed = false;

private static MethodInfo _updatePhase = null;
private static MethodInfo UpdatePhase {
get {
if (_updatePhase is null) {
try {
_updatePhase = typeof(Engine)
.GetMethod("UpdateInitPhase", BindingFlags.NonPublic | BindingFlags.Instance);
} catch (Exception ex) {
if (!failed)
{
Logger.WarnInternal("UpdatePhase not found: " + ex.ToString());
}
failed = true;
}
}
return _updatePhase;
}
}
private static MethodInfo _updateSubPhase = null;
private static MethodInfo UpdateSubPhase {
get {
if (_updatePhase is null) {
try {
_updateSubPhase = typeof(Engine)
.GetMethod("UpdateInitPhase", BindingFlags.NonPublic | BindingFlags.Instance);
} catch (Exception ex) {
if (!failed)
{
Logger.WarnInternal("UpdateSubPhase not found: " + ex.ToString());
}
failed = true;
}
}
return _updateSubPhase;
}
}

// Returned true means success, false means something went wrong.
internal static bool SetCustom(string text)
{
Expand All @@ -18,12 +56,8 @@ internal static bool SetCustom(string text)
// VerboseInit does extra logging, so turning it if off while we change the phase.
bool ogVerboseInit = Engine.Current.VerboseInit;
Engine.Current.VerboseInit = false;
Traverse.Create(Engine.Current)
.Method("UpdateInitPhase", new Type[] { typeof(string), typeof(bool) })
.GetValue("~ NeosModLoader ~", false);
Traverse.Create(Engine.Current)
.Method("UpdateInitSubphase", new Type[] { typeof(string), typeof(bool) })
.GetValue(text, false);
UpdatePhase.Invoke(Engine.Current, new object[] { "~ NeosModLoader ~", false });
UpdateSubPhase.Invoke(Engine.Current, new object[] { text, false });
Engine.Current.VerboseInit = ogVerboseInit;
return true;
}
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ Your Neos directory should now look similar to the following. Files not related

```
<Neos Install Directory>
│ 0Harmony.dll
│ Neos.exe
│ NeosLauncher.exe
Expand All @@ -32,11 +31,16 @@ Your Neos directory should now look similar to the following. Files not related
│ MotionBlurDisable.dll
│ NeosContactsSort.dll
| <More mods go here>
├───nml_libs
│ 0Harmony.dll
| <More libs go here>
└───Libraries
NeosModLoader.dll
```

Note that the libraries can also be in the root of the Neos install directory if you prefer, but the loading of those happens outside of NML itself.

## Finding Mods

A list of known mods is available in the [Neos Mod List](https://github.com/zkxs/neos-mod-list/blob/master/README.md). New mods and updates are also announced in [our Discord][Neos Modding Discord].
Expand Down
1 change: 1 addition & 0 deletions doc/modloader_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Not all keys are required to be present. Missing keys will use the defaults outl
| `debug` | `false` | If `true`, NeosMod.Debug() logs will appear in your log file. Otherwise, they are hidden. |
| `hidevisuals` | `false` | If `true`, NML won't show any visual indication of being installed, such as a loading indicator on the splash screen. |
| `nomods` | `false` | If `true`, mods will not be loaded. |
| `nolibraries` | `false` | If `true`, extra libraries will not be loaded. |
| `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. |