Release BlockModLoader 1.0.0 with loader whitelist and recommendations
This commit is contained in:
parent
d5d9fbf47c
commit
43a421d224
4 changed files with 301 additions and 89 deletions
|
|
@ -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)
|
||||
|
|
|
|||
Reference in a new issue