imnyang/BlockModLoader
Archived
Watch
2
0
Fork
You've already forked BlockModLoader
0

Release BlockModLoader 1.0.0 with loader whitelist and recommendations

This commit is contained in:
암냥 2026-10-05 14:02:51 +09:00
commit 43a421d224

View 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);
}
}