Release BlockModLoader 1.0.0 with loader whitelist and recommendations
This commit is contained in:
parent
d5d9fbf47c
commit
43a421d224
8 changed files with 301 additions and 89 deletions
31
BlockModLoader.csproj
Normal file
31
BlockModLoader.csproj
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)/$(MSBuildToolsVersion)/Microsoft.Common.props" />
|
||||
<PropertyGroup>
|
||||
<Configuration Condition="'$(Configuration)' == ''">Debug</Configuration>
|
||||
<ProjectGuid>{173C8DD2-70E0-4CA2-BC2B-9B839CE464A9}</ProjectGuid>
|
||||
<OutputType>Library</OutputType><TargetFrameworkVersion>v4.8</TargetFrameworkVersion><LangVersion>latest</LangVersion>
|
||||
<AssemblyName>BlockModLoader</AssemblyName><RootNamespace>BlockModLoader</RootNamespace>
|
||||
<PackageId>BlockModLoader</PackageId><Version>1.0.0</Version>
|
||||
<Authors>imnyang</Authors><Description>Blocks mod initialization under MelonLoader, BepInEx, or Unity Mod Manager unless explicitly whitelisted.</Description>
|
||||
<PackageLicenseExpression>GPL-3.0-only</PackageLicenseExpression>
|
||||
<PackageProjectUrl>https://git.mizuki.guru/imnyang/BlockModLoader</PackageProjectUrl>
|
||||
<RepositoryUrl>https://git.mizuki.guru/imnyang/BlockModLoader.git</RepositoryUrl>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
<PackageReadmeFile>README.md</PackageReadmeFile>
|
||||
<PackageOutputPath>$(MSBuildProjectDirectory)/artifacts</PackageOutputPath>
|
||||
<RestorePackagesPath>$(MSBuildProjectDirectory)/packages/nuget</RestorePackagesPath>
|
||||
<OutputPath>bin/$(Configuration)/</OutputPath><Optimize>true</Optimize>
|
||||
<TargetFrameworkRootPath Condition="Exists('packages/net48/build/.NETFramework/v4.8')">$(MSBuildProjectDirectory)/packages/net48/build/</TargetFrameworkRootPath>
|
||||
<FrameworkPathOverride Condition="Exists('packages/net48/build/.NETFramework/v4.8')">$(MSBuildProjectDirectory)/packages/net48/build/.NETFramework/v4.8</FrameworkPathOverride>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="NuGet.Build.Tasks.Pack" Version="6.11.1" PrivateAssets="all" />
|
||||
<None Include="README.md" Pack="true" PackagePath="/" />
|
||||
<None Include="LICENSE.md" Pack="true" PackagePath="/" />
|
||||
<Reference Include="mscorlib"><Private>false</Private></Reference>
|
||||
<Reference Include="System"><Private>false</Private></Reference>
|
||||
<Reference Include="System.Core"><Private>false</Private></Reference>
|
||||
<Compile Include="LoaderGuard.cs"/>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)/Microsoft.CSharp.targets"/>
|
||||
</Project>
|
||||
|
|
@ -4,25 +4,56 @@ using System.Collections.Generic;
|
|||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace BlockMelonLoader
|
||||
namespace BlockModLoader
|
||||
{
|
||||
[Flags]
|
||||
public enum WhiteListLoader
|
||||
{
|
||||
None = 0,
|
||||
MelonLoader = 1,
|
||||
BepInEx = 2,
|
||||
UMM = 4,
|
||||
All = MelonLoader | BepInEx | UMM
|
||||
}
|
||||
|
||||
public static class LoaderGuard
|
||||
{
|
||||
public static void EnsureAllowed(string modName)
|
||||
{
|
||||
EnsureAllowed(modName, AppDomain.CurrentDomain.GetAssemblies(), ShowAlert, Environment.FailFast);
|
||||
EnsureAllowed(modName, null);
|
||||
}
|
||||
|
||||
public static void EnsureAllowed(string modName, string recommendedLoader)
|
||||
{
|
||||
EnsureAllowed(modName, WhiteListLoader.None, recommendedLoader);
|
||||
}
|
||||
|
||||
public static void EnsureAllowed(string modName, WhiteListLoader whiteListLoader,
|
||||
string recommendedLoader = null)
|
||||
{
|
||||
EnsureAllowed(modName, AppDomain.CurrentDomain.GetAssemblies(), ShowAlert, Environment.FailFast,
|
||||
recommendedLoader, whiteListLoader);
|
||||
}
|
||||
|
||||
internal static void EnsureAllowed(string modName, IEnumerable<Assembly> assemblies,
|
||||
Action<string> showAlert, Action<string> terminate)
|
||||
Action<string> showAlert, Action<string> terminate, string recommendedLoader = null,
|
||||
WhiteListLoader whiteListLoader = WhiteListLoader.None)
|
||||
{
|
||||
if ((whiteListLoader & ~WhiteListLoader.All) != 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(whiteListLoader));
|
||||
|
||||
foreach (var assembly in assemblies)
|
||||
{
|
||||
if (!string.Equals(assembly.GetName().Name, "MelonLoader", StringComparison.OrdinalIgnoreCase))
|
||||
var loader = GetLoader(assembly.GetName().Name);
|
||||
if (loader == WhiteListLoader.None || (whiteListLoader & loader) != 0)
|
||||
continue;
|
||||
|
||||
var message = modName + " cannot run while MelonLoader is active.\n\n"
|
||||
+ "Remove MelonLoader and install this mod using Unity Mod Manager.\n\n"
|
||||
var loaderName = loader == WhiteListLoader.UMM ? "Unity Mod Manager (UMM)" : loader.ToString();
|
||||
var message = modName + " cannot run while " + loaderName + " is active.\n\n"
|
||||
+ "Remove the unsupported mod loader and follow this mod's installation instructions.\n\n"
|
||||
+ (string.IsNullOrWhiteSpace(recommendedLoader)
|
||||
? string.Empty
|
||||
: "Recommended mod loader: " + recommendedLoader.Trim() + ".\n\n")
|
||||
+ "The game will close after this message.";
|
||||
// Loader exception handlers can swallow a thrown exception, so termination
|
||||
// must happen even if the native dialog fails.
|
||||
|
|
@ -32,6 +63,18 @@ namespace BlockMelonLoader
|
|||
}
|
||||
}
|
||||
|
||||
private static WhiteListLoader GetLoader(string assemblyName)
|
||||
{
|
||||
if (string.Equals(assemblyName, "MelonLoader", StringComparison.OrdinalIgnoreCase))
|
||||
return WhiteListLoader.MelonLoader;
|
||||
if (string.Equals(assemblyName, "BepInEx", StringComparison.OrdinalIgnoreCase)
|
||||
|| string.Equals(assemblyName, "BepInEx.Core", StringComparison.OrdinalIgnoreCase))
|
||||
return WhiteListLoader.BepInEx;
|
||||
if (string.Equals(assemblyName, "UnityModManager", StringComparison.OrdinalIgnoreCase))
|
||||
return WhiteListLoader.UMM;
|
||||
return WhiteListLoader.None;
|
||||
}
|
||||
|
||||
private static void ShowAlert(string message)
|
||||
{
|
||||
Console.Error.WriteLine(message);
|
||||
|
|
|
|||
111
README.md
111
README.md
|
|
@ -1,36 +1,96 @@
|
|||
# BlockMelonLoader
|
||||
# BlockModLoader
|
||||
|
||||
A .NET Framework 4.8 library with no Unity or MelonLoader dependency.
|
||||
Call `BlockMelonLoader.LoaderGuard.EnsureAllowed("YourMod")` before initializing
|
||||
a mod. Building this library does not require a game installation.
|
||||
A .NET Framework 4.8 library with no Unity or mod loader dependency.
|
||||
Call `BlockModLoader.LoaderGuard.EnsureAllowed("YourMod")` before initializing
|
||||
a mod or game integration. Building this library does not require a game installation.
|
||||
|
||||
If an assembly named `MelonLoader` is loaded in the current AppDomain, the guard
|
||||
shows a Windows error dialog and calls `Environment.FailFast` after dismissal.
|
||||
Termination also happens if showing the dialog fails. On other platforms the
|
||||
message is written to standard error before termination; there is no native dialog.
|
||||
The guard does not inspect installation folders, modify loader files, or monitor
|
||||
assemblies loaded after the check. UMM and MelonLoader running together are blocked.
|
||||
By default, the guard blocks any of these loaded assemblies in the current AppDomain
|
||||
(exact names, case-insensitive):
|
||||
|
||||
Add a `PackageReference` dependency on `BlockMelonLoader` version `1.0.0`.
|
||||
Ship `BlockMelonLoader.dll` alongside your mod DLL; NuGet copies the runtime asset
|
||||
to the consuming project's build output.
|
||||
| Loader | Assembly names |
|
||||
| --- | --- |
|
||||
| MelonLoader | `MelonLoader` |
|
||||
| BepInEx | `BepInEx`, `BepInEx.Core` |
|
||||
| Unity Mod Manager (UMM) | `UnityModManager` |
|
||||
|
||||
This is an initialization guard, not a MelonLoader plugin. Simply placing the
|
||||
UMM-only mod DLL in MelonLoader's Mods folder will not invoke the guard.
|
||||
A bridge that invokes the UMM entry point is checked; direct assembly loading
|
||||
without invoking that entry point is not. Modified binaries can bypass this check.
|
||||
The BepInEx assembly names follow the [BepInEx 5 API](https://docs.bepinex.dev/api/index.html)
|
||||
and [BepInEx 6 API](https://docs.bepinex.dev/master/api/index.html); UMM uses
|
||||
[`UnityModManager`](https://github.com/newman55/unity-mod-manager/blob/master/UnityModManager/UnityModManager.csproj).
|
||||
|
||||
When a non-whitelisted supported loader is detected, the guard names it in a Windows error
|
||||
dialog and calls `Environment.FailFast` after dismissal, terminating the entire
|
||||
game process. Termination also happens if showing the dialog fails. On other
|
||||
platforms the message is written to standard error before termination.
|
||||
When multiple loaders are present, the first detected non-whitelisted loader triggers
|
||||
one dialog and termination. Without a whitelist, all three loaders are blocked.
|
||||
|
||||
The guard checks only assemblies already loaded when `EnsureAllowed` is called.
|
||||
It does not inspect installation folders, modify loader files, or monitor later
|
||||
assembly loads. Names such as `MelonLoader.Helper` or `BepInEx.Helper` are not
|
||||
matched. Renamed or modified loader binaries can bypass the check.
|
||||
|
||||
This is an initialization guard. Merely placing its DLL in a game or loader
|
||||
folder will not invoke it: the consuming mod or game must call `EnsureAllowed`.
|
||||
|
||||
## Allowed loaders
|
||||
|
||||
Use `WhiteListLoader` to allow selected loaders for each guard call:
|
||||
|
||||
```csharp
|
||||
using BlockModLoader;
|
||||
|
||||
// Allow BepInEx; block MelonLoader and UMM.
|
||||
LoaderGuard.EnsureAllowed("YourMod", whiteListLoader: WhiteListLoader.BepInEx,
|
||||
recommendedLoader: "BepInEx");
|
||||
|
||||
// Allow both BepInEx and UMM; block MelonLoader.
|
||||
LoaderGuard.EnsureAllowed("YourMod", WhiteListLoader.BepInEx | WhiteListLoader.UMM);
|
||||
```
|
||||
|
||||
Available values are `None`, `MelonLoader`, `BepInEx`, `UMM`, and `All`.
|
||||
Combine values with `|`. `None` (the default for existing calls) blocks all three;
|
||||
`All` allows all three. `BepInEx` allows both `BepInEx` and `BepInEx.Core`.
|
||||
An allowed loader does not prevent detection of another blocked loader in the
|
||||
same AppDomain. Unknown flag values throw `ArgumentOutOfRangeException`.
|
||||
The whitelist applies only to these three known loaders; other assemblies are
|
||||
still ignored. Each call supplies its own whitelist, with no shared global setting.
|
||||
|
||||
## Recommended loader in the alert
|
||||
|
||||
Pass an optional display name as the second argument to include a recommendation
|
||||
in the alert:
|
||||
|
||||
```csharp
|
||||
BlockModLoader.LoaderGuard.EnsureAllowed("YourMod", recommendedLoader: "YourPreferredLoader");
|
||||
```
|
||||
|
||||
The alert adds `Recommended mod loader: YourPreferredLoader.` before the game
|
||||
closure notice. Omit the second argument, or pass null, an empty string, or
|
||||
whitespace, to keep the original generic guidance. Leading and trailing
|
||||
whitespace is trimmed. This setting changes only the alert text; it does not
|
||||
allow the recommended loader or change which loaders are blocked.
|
||||
|
||||
## Migration from BlockMelonLoader
|
||||
|
||||
Replace the `BlockMelonLoader` package reference with `BlockModLoader` version
|
||||
`1.0.0`, update imports to `using BlockModLoader;`, and ship `BlockModLoader.dll`
|
||||
alongside your mod DLL. NuGet copies the runtime asset to the consuming project's
|
||||
build output. The `LoaderGuard.EnsureAllowed("YourMod")` method remains the same,
|
||||
but now blocks BepInEx and UMM as well as MelonLoader.
|
||||
|
||||
## Build and test
|
||||
|
||||
On Windows, run `./build.ps1 -Test`. Visual Studio or Rider MSBuild is required;
|
||||
use `-MSBuildPath` to select it explicitly. The script downloads .NET Framework
|
||||
4.8 reference assemblies into the ignored `packages` folder when needed.
|
||||
The DLL is written to `bin/Release/BlockMelonLoader.dll` and the NuGet package to
|
||||
`artifacts/BlockMelonLoader.1.0.0.nupkg`. Packaging uses `NuGet.Build.Tasks.Pack`.
|
||||
The DLL is written to `bin/Release/BlockModLoader.dll` and the NuGet package to
|
||||
`artifacts/BlockModLoader.1.0.0.nupkg`. Packaging uses `NuGet.Build.Tasks.Pack`.
|
||||
|
||||
The standalone tests check detection, notification ordering, and dialog failure
|
||||
handling. They substitute the dialog and termination callbacks and do not crash
|
||||
the game. Run them directly with `Tests/bin/Release/BlockMelonLoader.Tests.exe`.
|
||||
The standalone tests check every loader name, case-insensitive detection,
|
||||
unrelated assemblies, multiple loaders, notification ordering, and dialog failure
|
||||
handling, plus every whitelist combination and invalid flags.
|
||||
They substitute the dialog and termination callbacks and do not crash
|
||||
the game. Run them directly with `Tests/bin/Release/BlockModLoader.Tests.exe`.
|
||||
|
||||
## Installation
|
||||
|
||||
|
|
@ -40,16 +100,17 @@ The NuGet feed for this project is:
|
|||
https://git.mizuki.guru/api/packages/imnyang/nuget/index.json
|
||||
```
|
||||
|
||||
Add that feed to the consuming project's `NuGet.Config`, then add the dependency:
|
||||
After the new package has been published to that feed, add it to the consuming
|
||||
project's `NuGet.Config`, then add the dependency:
|
||||
|
||||
```xml
|
||||
<PackageReference Include="BlockMelonLoader" Version="[1.0.0]" />
|
||||
<PackageReference Include="BlockModLoader" Version="[1.0.0]" />
|
||||
```
|
||||
|
||||
For local development, build this repository and use the generated `.nupkg` in
|
||||
a local NuGet feed. The source repository is not required when building a consumer.
|
||||
New library versions must use a new package version and update the consumer's
|
||||
dependency version.
|
||||
dependency version. The source repository is https://git.mizuki.guru/imnyang/BlockModLoader.
|
||||
|
||||
## License
|
||||
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using BlockMelonLoader;
|
||||
|
||||
internal static class BlockMelonLoaderTests
|
||||
{
|
||||
internal static void Run()
|
||||
{
|
||||
var alerts = 0;
|
||||
var terminations = 0;
|
||||
Action<string> alert = message =>
|
||||
{
|
||||
if (!message.Contains("SampleMod") || !message.Contains("Unity Mod Manager"))
|
||||
throw new Exception("Missing mod name or installation guidance.");
|
||||
alerts++;
|
||||
};
|
||||
Action<string> terminate = message =>
|
||||
{
|
||||
if (alerts == 0) throw new Exception("Termination preceded notification.");
|
||||
terminations++;
|
||||
};
|
||||
|
||||
var unrelated = AppDomain.CurrentDomain.DefineDynamicAssembly(
|
||||
new AssemblyName("MelonLoader.Helper"), AssemblyBuilderAccess.Run);
|
||||
LoaderGuard.EnsureAllowed("SampleMod", new[] { typeof(LoaderGuard).Assembly, unrelated }, alert, terminate);
|
||||
if (alerts != 0 || terminations != 0) throw new Exception("Blocked an unrelated assembly.");
|
||||
|
||||
var loader = AppDomain.CurrentDomain.DefineDynamicAssembly(
|
||||
new AssemblyName("melonloader"), AssemblyBuilderAccess.Run);
|
||||
LoaderGuard.EnsureAllowed("SampleMod", new[] { loader }, alert, terminate);
|
||||
if (alerts != 1 || terminations != 1) throw new Exception("MelonLoader was not blocked.");
|
||||
|
||||
try
|
||||
{
|
||||
LoaderGuard.EnsureAllowed("SampleMod", new[] { loader },
|
||||
message => { throw new InvalidOperationException("Dialog unavailable."); },
|
||||
message => terminations++);
|
||||
throw new Exception("Expected the simulated dialog failure.");
|
||||
}
|
||||
catch (InvalidOperationException) { }
|
||||
if (terminations != 2) throw new Exception("Dialog failure bypassed termination.");
|
||||
}
|
||||
}
|
||||
18
Tests/BlockModLoader.Tests.csproj
Normal file
18
Tests/BlockModLoader.Tests.csproj
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<Project ToolsVersion="Current" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(MSBuildExtensionsPath)/$(MSBuildToolsVersion)/Microsoft.Common.props"/>
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType><AssemblyName>BlockModLoader.Tests</AssemblyName><TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||
<LangVersion>latest</LangVersion><OutputPath>bin/Release/</OutputPath>
|
||||
<TargetFrameworkRootPath Condition="Exists('../packages/net48/build/.NETFramework/v4.8')">$(MSBuildProjectDirectory)/../packages/net48/build/</TargetFrameworkRootPath>
|
||||
<FrameworkPathOverride Condition="Exists('../packages/net48/build/.NETFramework/v4.8')">$(MSBuildProjectDirectory)/../packages/net48/build/.NETFramework/v4.8</FrameworkPathOverride>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<Reference Include="mscorlib"><Private>false</Private></Reference>
|
||||
<Reference Include="System"><Private>false</Private></Reference>
|
||||
<Reference Include="System.Core"><Private>false</Private></Reference>
|
||||
<Compile Include="Program.cs"/>
|
||||
<Compile Include="BlockModLoaderTests.cs"/>
|
||||
<Compile Include="../LoaderGuard.cs" Link="LoaderGuard.cs"/>
|
||||
</ItemGroup>
|
||||
<Import Project="$(MSBuildToolsPath)/Microsoft.CSharp.targets"/>
|
||||
</Project>
|
||||
152
Tests/BlockModLoaderTests.cs
Normal file
152
Tests/BlockModLoaderTests.cs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
using System;
|
||||
using System.Reflection;
|
||||
using System.Reflection.Emit;
|
||||
using BlockModLoader;
|
||||
|
||||
internal static class BlockModLoaderTests
|
||||
{
|
||||
internal static void Run()
|
||||
{
|
||||
var unrelated = new[]
|
||||
{
|
||||
typeof(LoaderGuard).Assembly, CreateAssembly("MelonLoader.Helper"),
|
||||
CreateAssembly("BepInEx.Helper"), CreateAssembly("BepInEx.Core.Helper"),
|
||||
CreateAssembly("UnityModManager.Helper"), CreateAssembly("Harmony")
|
||||
};
|
||||
Action<string> unexpected = message => { throw new Exception("Blocked an unrelated assembly."); };
|
||||
LoaderGuard.EnsureAllowed("SampleMod", new Assembly[0], unexpected, unexpected);
|
||||
LoaderGuard.EnsureAllowed("SampleMod", unrelated, unexpected, unexpected);
|
||||
|
||||
CheckLoader("MelonLoader", "MelonLoader");
|
||||
CheckLoader("melonloader", "MelonLoader");
|
||||
CheckLoader("BepInEx", "BepInEx");
|
||||
CheckLoader("bepinex", "BepInEx");
|
||||
CheckLoader("BepInEx.Core", "BepInEx");
|
||||
CheckLoader("bepinex.core", "BepInEx");
|
||||
CheckLoader("UnityModManager", "Unity Mod Manager (UMM)");
|
||||
CheckLoader("unitymodmanager", "Unity Mod Manager (UMM)");
|
||||
CheckWhiteList();
|
||||
|
||||
foreach (var loaderName in new[] { "MelonLoader", "BepInEx", "BepInEx.Core", "UnityModManager" })
|
||||
{
|
||||
foreach (var recommendation in new[] { null, "", " \t ", "CustomLoader", " UMM " })
|
||||
CheckRecommendation(loaderName, recommendation);
|
||||
}
|
||||
LoaderGuard.EnsureAllowed("SampleMod", unrelated, unexpected, unexpected, "CustomLoader");
|
||||
|
||||
var alerts = 0;
|
||||
var terminations = 0;
|
||||
LoaderGuard.EnsureAllowed("SampleMod", new[]
|
||||
{ unrelated[1], CreateAssembly("MelonLoader"), CreateAssembly("BepInEx.Core"), CreateAssembly("UnityModManager") },
|
||||
message => alerts++, message => terminations++);
|
||||
if (alerts != 1 || terminations != 1)
|
||||
throw new Exception("Multiple loaders should trigger only one notification and termination.");
|
||||
}
|
||||
|
||||
private static void CheckWhiteList()
|
||||
{
|
||||
var names = new[] { "melonloader", "bepinex", "bepinex.core", "unitymodmanager" };
|
||||
var types = new[] { WhiteListLoader.MelonLoader, WhiteListLoader.BepInEx,
|
||||
WhiteListLoader.BepInEx, WhiteListLoader.UMM };
|
||||
for (var flags = 0; flags <= (int)WhiteListLoader.All; flags++)
|
||||
{
|
||||
var whiteList = (WhiteListLoader)flags;
|
||||
for (var i = 0; i < names.Length; i++)
|
||||
{
|
||||
var alerts = 0;
|
||||
var terminations = 0;
|
||||
LoaderGuard.EnsureAllowed("SampleMod", new[] { CreateAssembly(names[i]) },
|
||||
message => alerts++, message => terminations++, "CustomLoader", whiteList);
|
||||
var expected = (whiteList & types[i]) != 0 ? 0 : 1;
|
||||
if (alerts != expected || terminations != expected)
|
||||
throw new Exception("Whitelist incorrectly handled " + names[i] + " with " + whiteList);
|
||||
}
|
||||
|
||||
var mixedAlerts = 0;
|
||||
var mixedTerminations = 0;
|
||||
LoaderGuard.EnsureAllowed("SampleMod", new[] { CreateAssembly("BepInEx.Core"),
|
||||
CreateAssembly("UnityModManager"), CreateAssembly("MelonLoader") },
|
||||
message => mixedAlerts++, message => mixedTerminations++, null, whiteList);
|
||||
var mixedExpected = whiteList == WhiteListLoader.All ? 0 : 1;
|
||||
if (mixedAlerts != mixedExpected || mixedTerminations != mixedExpected)
|
||||
throw new Exception("An allowed loader bypassed detection of a blocked loader.");
|
||||
}
|
||||
|
||||
foreach (var invalid in new[] { (WhiteListLoader)8, (WhiteListLoader)(-1) })
|
||||
{
|
||||
try
|
||||
{
|
||||
LoaderGuard.EnsureAllowed("SampleMod", new Assembly[0],
|
||||
message => { }, message => { }, null, invalid);
|
||||
throw new Exception("Expected invalid whitelist flags to be rejected.");
|
||||
}
|
||||
catch (ArgumentOutOfRangeException) { }
|
||||
}
|
||||
}
|
||||
|
||||
private static void CheckRecommendation(string loaderName, string recommendation)
|
||||
{
|
||||
string alertMessage = null;
|
||||
var terminations = 0;
|
||||
LoaderGuard.EnsureAllowed("SampleMod", new[] { CreateAssembly(loaderName) },
|
||||
message =>
|
||||
{
|
||||
alertMessage = message;
|
||||
if (string.IsNullOrWhiteSpace(recommendation))
|
||||
{
|
||||
if (message.Contains("Recommended mod loader:"))
|
||||
throw new Exception("An empty recommendation should not be displayed.");
|
||||
}
|
||||
else if (!message.Contains("Recommended mod loader: " + recommendation.Trim() + ".\n\n"))
|
||||
throw new Exception("The configured recommendation was not displayed.");
|
||||
},
|
||||
message =>
|
||||
{
|
||||
if (alertMessage == null || message != alertMessage)
|
||||
throw new Exception("Recommended loader message was lost before termination.");
|
||||
terminations++;
|
||||
}, recommendation);
|
||||
if (terminations != 1)
|
||||
throw new Exception("A recommendation changed loader blocking behavior.");
|
||||
}
|
||||
|
||||
private static void CheckLoader(string assemblyName, string loaderName)
|
||||
{
|
||||
var loader = CreateAssembly(assemblyName);
|
||||
var alerts = 0;
|
||||
var terminations = 0;
|
||||
string alertMessage = null;
|
||||
LoaderGuard.EnsureAllowed("SampleMod", new[] { typeof(LoaderGuard).Assembly, loader },
|
||||
message =>
|
||||
{
|
||||
if (!message.Contains("SampleMod cannot run while " + loaderName + " is active.")
|
||||
|| !message.Contains("follow this mod's installation instructions.")
|
||||
|| !message.Contains("The game will close"))
|
||||
throw new Exception("Missing mod name, detected loader, or installation guidance.");
|
||||
alertMessage = message;
|
||||
alerts++;
|
||||
},
|
||||
message =>
|
||||
{
|
||||
if (alerts != 1 || message != alertMessage)
|
||||
throw new Exception("Termination preceded notification or used a different message.");
|
||||
terminations++;
|
||||
});
|
||||
if (alerts != 1 || terminations != 1) throw new Exception(assemblyName + " was not blocked.");
|
||||
|
||||
try
|
||||
{
|
||||
LoaderGuard.EnsureAllowed("SampleMod", new[] { loader },
|
||||
message => { throw new InvalidOperationException("Dialog unavailable."); },
|
||||
message => terminations++);
|
||||
throw new Exception("Expected the simulated dialog failure.");
|
||||
}
|
||||
catch (InvalidOperationException) { }
|
||||
if (terminations != 2) throw new Exception("Dialog failure bypassed termination for " + assemblyName);
|
||||
}
|
||||
|
||||
private static Assembly CreateAssembly(string name)
|
||||
{
|
||||
return AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(name), AssemblyBuilderAccess.Run);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,8 @@ internal static class Program
|
|||
{
|
||||
try
|
||||
{
|
||||
BlockMelonLoaderTests.Run();
|
||||
Console.WriteLine("BlockMelonLoader tests passed.");
|
||||
BlockModLoaderTests.Run();
|
||||
Console.WriteLine("BlockModLoader tests passed.");
|
||||
return 0;
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
|
|
|||
10
build.ps1
10
build.ps1
|
|
@ -28,15 +28,15 @@ try {
|
|||
Remove-Item -LiteralPath $archive
|
||||
}
|
||||
|
||||
& $MSBuildPath BlockMelonLoader.csproj /restore '/t:Rebuild,Pack' /p:Configuration=Release /v:minimal /nologo
|
||||
& $MSBuildPath BlockModLoader.csproj /restore '/t:Rebuild,Pack' /p:Configuration=Release /v:minimal /nologo
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Library build failed.' }
|
||||
if ($Test) {
|
||||
& $MSBuildPath Tests/BlockMelonLoader.Tests.csproj /t:Rebuild /v:minimal /nologo
|
||||
& $MSBuildPath Tests/BlockModLoader.Tests.csproj /t:Rebuild /v:minimal /nologo
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Test build failed.' }
|
||||
& ./Tests/bin/Release/BlockMelonLoader.Tests.exe
|
||||
& ./Tests/bin/Release/BlockModLoader.Tests.exe
|
||||
if ($LASTEXITCODE -ne 0) { throw 'Tests failed.' }
|
||||
}
|
||||
Write-Host "Library output: $PSScriptRoot/bin/Release/BlockMelonLoader.dll"
|
||||
Write-Host "NuGet output: $PSScriptRoot/artifacts/BlockMelonLoader.1.0.0.nupkg"
|
||||
Write-Host "Library output: $PSScriptRoot/bin/Release/BlockModLoader.dll"
|
||||
Write-Host "NuGet output: $PSScriptRoot/artifacts/BlockModLoader.1.0.0.nupkg"
|
||||
}
|
||||
finally { Pop-Location }
|
||||
|
|
|
|||
Reference in a new issue