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