Some mods target the Mono version of the game, which is available by opting into the Steam beta branch "alternate"
Decompiled source of OTCLoader v1.0.5
Plugins/OverTheCounter-Loader.dll
Decompiled 2 days agousing System; using System.Diagnostics; using System.IO; using System.Reflection; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using MelonLoader; using MelonLoader.Utils; using Mono.Cecil; using Mono.Collections.Generic; using Newtonsoft.Json; using OverTheCounter.Loader; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: MelonInfo(typeof(LoaderPlugin), "OTC Loader", "1.0.5", "hdlmrell", null)] [assembly: MelonColor(100, 200, 180, 255)] [assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")] [assembly: AssemblyVersion("0.0.0.0")] namespace OverTheCounter.Loader; internal enum Branch { Mono, Il2Cpp } [Serializable] internal class LoaderConfig { public string[] Whitelist = Array.Empty<string>(); } public class LoaderPlugin : MelonPlugin { private const string DisabledExt = ".off"; private const string ConfigFileName = "OTCLoader.config.json"; private static readonly string[] BuiltinBlacklist = new string[2] { "OverTheCounter-Loader.dll", "SwapperPlugin.dll" }; private const uint MB_OKCANCEL = 1u; private const uint MB_YESNO = 4u; private const uint MB_ICONQUESTION = 32u; private const uint MB_ICONWARNING = 48u; private const uint MB_ICONINFORMATION = 64u; private const int IDOK = 1; private const int IDCANCEL = 2; private const int IDYES = 6; private const int IDNO = 7; private static readonly Instance Logger = new Instance("OTC Loader"); private static LoaderConfig _config = new LoaderConfig(); private static string _configPath = ""; [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)] private static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type); public override void OnPreInitialization() { if (AppDomain.CurrentDomain.GetData("OTC_LOADER_INITIALIZED") != null) { Logger.Msg("Another OTC Loader instance already ran — skipping this copy."); return; } AppDomain.CurrentDomain.SetData("OTC_LOADER_INITIALIZED", true); string modsDirectory = MelonEnvironment.ModsDirectory; if (!Directory.Exists(modsDirectory)) { return; } LoadConfig(); Branch branch = (MelonUtils.IsGameIl2Cpp() ? Branch.Il2Cpp : Branch.Mono); string text = ((branch == Branch.Il2Cpp) ? "IL2CPP" : "Mono"); string text2 = ((branch == Branch.Il2Cpp) ? "Mono" : "IL2CPP"); string[] array = new string[256]; int num = 0; string[] files = Directory.GetFiles(modsDirectory, "*", SearchOption.AllDirectories); foreach (string text3 in files) { bool flag = text3.EndsWith(".dll.off", StringComparison.OrdinalIgnoreCase); bool flag2 = text3.EndsWith(".dll.off.di", StringComparison.OrdinalIgnoreCase); bool flag3 = text3.EndsWith(".dll.di", StringComparison.OrdinalIgnoreCase); if (!flag && !flag2 && !flag3) { continue; } string text4 = (flag2 ? ".off.di" : (flag3 ? ".di" : ".off")); string text5 = text3.Substring(0, text3.Length - text4.Length); try { if (!File.Exists(text5)) { File.Move(text3, text5); if ((flag || flag2) && num < array.Length) { array[num++] = Path.GetFileName(text5); } } else { File.Delete(text3); } } catch (Exception ex) { Logger.Warning("Could not restore " + Path.GetFileName(text3) + ": " + ex.Message); } } string[] files2 = Directory.GetFiles(modsDirectory, "*.dll", SearchOption.AllDirectories); bool[] array2 = new bool[files2.Length]; Branch?[] array3 = new Branch?[files2.Length]; for (int j = 0; j < files2.Length; j++) { string fileName = Path.GetFileName(files2[j]); array2[j] = IsBlacklisted(fileName); if (!array2[j]) { array3[j] = DetectBranch(files2[j]); } } for (int k = 0; k < files2.Length; k++) { if (array2[k] || !array3[k].HasValue) { continue; } string directoryName = Path.GetDirectoryName(files2[k]); if (!Path.GetFileName(directoryName).Equals("Plugins", StringComparison.OrdinalIgnoreCase)) { continue; } string b = StripBranchKeyword(Path.GetFileName(files2[k])); bool flag4 = false; for (int l = 0; l < files2.Length; l++) { if (l != k && !array2[l] && string.Equals(Path.GetDirectoryName(files2[l]), directoryName, StringComparison.OrdinalIgnoreCase) && string.Equals(StripBranchKeyword(Path.GetFileName(files2[l])), b, StringComparison.OrdinalIgnoreCase)) { flag4 = true; break; } } if (!flag4) { array2[k] = true; } } int num2 = 0; string[] array4 = new string[files2.Length]; int num3 = 0; string[] array5 = new string[files2.Length]; string[] array6 = new string[files2.Length]; string[] array7 = new string[files2.Length]; int num4 = 0; for (int m = 0; m < files2.Length; m++) { if (array2[m] || !array3[m].HasValue || array3[m] == branch) { continue; } string text6 = files2[m]; string fileName2 = Path.GetFileName(text6); string directoryName2 = Path.GetDirectoryName(text6); bool flag5 = false; try { File.Move(text6, text6 + ".off"); num2++; for (int n = 0; n < num; n++) { if (string.Equals(array[n], fileName2, StringComparison.OrdinalIgnoreCase)) { flag5 = true; break; } } if (!flag5) { Logger.Msg("Disabled '" + fileName2 + "' — targets " + text2 + " but game is " + text + "."); if (num3 < array4.Length) { array4[num3++] = text6; } } } catch (Exception ex2) { Logger.Warning("Could not disable '" + fileName2 + "': " + ex2.Message); continue; } bool flag6 = string.Equals(directoryName2, modsDirectory, StringComparison.OrdinalIgnoreCase); bool flag7 = false; for (int num5 = 0; num5 < num4; num5++) { if (!flag6 && string.Equals(array5[num5], directoryName2, StringComparison.OrdinalIgnoreCase)) { flag7 = true; break; } } if (flag7) { continue; } bool flag8 = false; string b2 = StripBranchKeyword(fileName2); for (int num6 = 0; num6 < files2.Length; num6++) { if (num6 != m && !array2[num6] && (!array3[num6].HasValue || array3[num6] == branch)) { bool num7 = !flag6 && string.Equals(Path.GetDirectoryName(files2[num6]), directoryName2, StringComparison.OrdinalIgnoreCase); bool flag9 = string.Equals(StripBranchKeyword(Path.GetFileName(files2[num6])), b2, StringComparison.OrdinalIgnoreCase); if (num7 || flag9) { flag8 = true; break; } } } if (flag8) { if (flag5) { continue; } string text7 = ""; for (int num8 = 0; num8 < files2.Length; num8++) { if (num8 != m && !array2[num8] && (!array3[num8].HasValue || array3[num8] == branch)) { bool num9 = !flag6 && string.Equals(Path.GetDirectoryName(files2[num8]), directoryName2, StringComparison.OrdinalIgnoreCase); bool flag10 = string.Equals(StripBranchKeyword(Path.GetFileName(files2[num8])), b2, StringComparison.OrdinalIgnoreCase); if (num9 || flag10) { text7 = Path.GetFileName(files2[num8]); break; } } } Logger.Msg(" → Compatible version kept: " + text7); continue; } string fileNameWithoutExtension = Path.GetFileNameWithoutExtension(fileName2); string text8 = ""; for (int num10 = 0; num10 < files2.Length; num10++) { if (!array2[num10] && array3[num10].HasValue && array3[num10] != branch && string.Equals(Path.GetDirectoryName(files2[num10]), directoryName2, StringComparison.OrdinalIgnoreCase) && (!flag6 || string.Equals(fileName2, Path.GetFileName(files2[num10]), StringComparison.OrdinalIgnoreCase))) { if (text8.Length > 0) { text8 += ", "; } text8 += Path.GetFileName(files2[num10]); } } array5[num4] = directoryName2; array6[num4] = fileNameWithoutExtension; array7[num4] = text8; num4++; } if (num4 > 0) { Logger.Warning("╔══════════════════════════════════════════════════════════╗"); Logger.Warning("║ INCOMPATIBLE MODS — no " + text + "-compatible version found"); Logger.Warning("║"); for (int num11 = 0; num11 < num4; num11++) { Logger.Warning("║ • " + array6[num11]); Logger.Warning("║ Disabled: " + array7[num11]); } Logger.Warning("║"); Logger.Warning("║ → Check each mod page for a " + text + "-compatible release,"); Logger.Warning("║ or switch your game to the branch those mods support."); Logger.Warning("║"); Logger.Warning("║ → Think this is a mistake? Open the config file and"); Logger.Warning("║ add the filename to the Whitelist:"); Logger.Warning("║ " + _configPath); Logger.Warning("╚══════════════════════════════════════════════════════════╝"); } if (num2 > 0 || num4 > 0) { string text9 = ""; for (int num12 = 0; num12 < num4; num12++) { if (text9.Length > 0) { text9 += ", "; } text9 += array6[num12]; } string text10 = ((num4 > 0) ? (": " + text9) : ""); Logger.Msg("Done — " + num2 + " wrong-branch DLL(s) correctly handled, " + num4 + " mod(s) have no compatible version" + text10 + "."); } else { Logger.Msg("All DLLs are compatible with " + text + "."); } if (num3 > 0) { InteractiveFlow(array4, num3, branch, files2, array2, array3, modsDirectory); } } private static void InteractiveFlow(string[] firstTimePaths, int count, Branch gameBranch, string[] allDlls, bool[] skip, Branch?[] branches, string modsPath) { string text = ((gameBranch == Branch.Il2Cpp) ? "IL2CPP" : "Mono"); string text2 = ((gameBranch == Branch.Il2Cpp) ? "Mono" : "IL2CPP"); string[] array = new string[count]; int num = 0; for (int i = 0; i < count; i++) { string text3 = firstTimePaths[i]; string? fileName = Path.GetFileName(text3); string directoryName = Path.GetDirectoryName(text3); string b = StripBranchKeyword(fileName); bool flag = false; for (int j = 0; j < allDlls.Length; j++) { if (j != i && !skip[j] && (!branches[j].HasValue || branches[j] == gameBranch)) { bool num2 = !string.Equals(directoryName, modsPath, StringComparison.OrdinalIgnoreCase) && string.Equals(Path.GetDirectoryName(allDlls[j]), directoryName, StringComparison.OrdinalIgnoreCase); bool flag2 = string.Equals(StripBranchKeyword(Path.GetFileName(allDlls[j])), b, StringComparison.OrdinalIgnoreCase); if (num2 || flag2) { flag = true; break; } } } if (!flag) { array[num++] = text3; } } bool flag3 = false; try { if (_config.Whitelist != null && _config.Whitelist.Length != 0) { string text4 = "OTC Loader — Whitelist Management\n\nYour whitelist contains " + _config.Whitelist.Length + " mod(s).\nKeep the current whitelist or clear it entirely?\n\n[OK] — (Recommended) Keep current whitelist\n[Cancel] — Clear whitelist"; if (MessageBox(IntPtr.Zero, text4, "OTC Loader", 33u) == 2) { _config.Whitelist = Array.Empty<string>(); flag3 = true; Logger.Msg("User chose to clear the whitelist."); } } bool flag4 = false; if (num > 0) { string text5 = ""; for (int k = 0; k < num; k++) { if (k > 0) { text5 += "\n"; } text5 = text5 + "• " + Path.GetFileName(array[k]); if (k >= 9) { text5 = text5 + "\n...and " + (num - 10) + " more."; break; } } string text6 = "OTC Loader — Incompatible Mods Detected\n\nThese mods target " + text2 + " but your game runs " + text + ".\nNo compatible versions were found, so they were disabled:\n\n" + text5 + "\n\nKeep all of them disabled, or review each mod to optionally whitelist them?\n\n[OK] — (Recommended) Keep all disabled\n[Cancel] — Review each mod"; flag4 = MessageBox(IntPtr.Zero, text6, "OTC Loader", 49u) == 2; } if (flag4) { for (int l = 0; l < num; l++) { string fileName2 = Path.GetFileName(array[l]); string text7 = "OTC Loader — Review Mod\n\n" + fileName2 + "\n\nThis mod targets " + text2 + " but your game runs " + text + ".\n\nKeep this mod disabled, or add it to the whitelist? (May cause crashes if whitelisted)\n\n[OK] — (Recommended) Keep disabled\n[Cancel] — Whitelist this mod"; if (MessageBox(IntPtr.Zero, text7, "OTC Loader", 33u) == 2) { string[] array2 = _config.Whitelist ?? Array.Empty<string>(); string[] array3 = new string[array2.Length + 1]; Array.Copy(array2, array3, array2.Length); array3[array2.Length] = fileName2; _config.Whitelist = array3; flag3 = true; Logger.Msg("User whitelisted: " + fileName2); } } } if (flag3) { SaveConfig(); } } catch (Exception ex) { Logger.Warning("Interactive prompt failed (falling back to log-only): " + ex.Message); } string text8 = ""; for (int m = 0; m < count; m++) { if (text8.Length > 0) { text8 += ", "; } text8 += Path.GetFileNameWithoutExtension(firstTimePaths[m]); } Logger.Warning("First-time disable of: " + text8); Logger.Warning("A restart is recommended so the disabled DLLs are fully unloaded."); string text9 = "SAFE TO RUN! Your compatible mods will work fine.\n\nOTC Loader safely disabled these incompatible mod files:\n" + text8 + "\n\nThe runtime may have already cached the old files. A restart is recommended.\n\n[OK] — (Recommended) Close game NOW. Restart via your mod manager.\n[Cancel] — Continue anyway (may cause errors)"; try { if (MessageBox(IntPtr.Zero, text9, "OTC Loader — Restart Recommended", 65u) == 1) { Environment.Exit(0); } } catch { Logger.Warning("╔══════════════════════════════════════════════════════════╗"); Logger.Warning("║ RESTART RECOMMENDED ║"); Logger.Warning("║ ║"); Logger.Warning("║ Incompatible mods were disabled but may have already ║"); Logger.Warning("║ been cached. Please close and restart the game. ║"); Logger.Warning("║ Affected: " + text8); Logger.Warning("╚══════════════════════════════════════════════════════════╝"); } } private static void LoadConfig() { _configPath = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "OTCLoader.config.json"); string configPath = _configPath; if (File.Exists(configPath)) { try { _config = JsonConvert.DeserializeObject<LoaderConfig>(File.ReadAllText(configPath)) ?? new LoaderConfig(); return; } catch (Exception ex) { Logger.Warning("Could not read config, using defaults: " + ex.Message); _config = new LoaderConfig(); return; } } _config = new LoaderConfig(); try { File.WriteAllText(configPath, "{\n \"_readme\": \"Add DLL filenames to Whitelist to prevent OTC Loader from disabling them.\",\n \"Whitelist\": [\n \"ExampleMod-IL2Cpp.dll\",\n \"AnotherExampleMod-IL2Cpp.dll\"\n ]\n}\n"); Logger.Msg("Created default config at " + configPath); } catch { } } private static void SaveConfig() { if (string.IsNullOrEmpty(_configPath)) { return; } try { string contents = JsonConvert.SerializeObject((object)_config, (Formatting)1); File.WriteAllText(_configPath, contents); } catch (Exception ex) { Logger.Warning("Could not save config: " + ex.Message); } } private static string StripBranchKeyword(string filename) { string text = Path.GetFileNameWithoutExtension(filename).ToLowerInvariant(); string[] array = new string[6] { "-il2cpp", "_il2cpp", ".il2cpp", "-mono", "_mono", ".mono" }; foreach (string text2 in array) { if (text.EndsWith(text2)) { return text.Substring(0, text.Length - text2.Length); } } return text; } private static bool IsBlacklisted(string filename) { if (!Array.Exists(BuiltinBlacklist, (string b) => string.Equals(b, filename, StringComparison.OrdinalIgnoreCase)) && !filename.StartsWith("S1API", StringComparison.OrdinalIgnoreCase)) { return Array.Exists(_config.Whitelist ?? Array.Empty<string>(), (string w) => string.Equals(w, filename, StringComparison.OrdinalIgnoreCase)); } return true; } private static Branch? DetectBranch(string path) { //IL_0042: Unknown result type (might be due to invalid IL or missing references) //IL_0047: Unknown result type (might be due to invalid IL or missing references) string text = Path.GetFileName(path).ToLowerInvariant(); if (text.Contains("mono")) { return Branch.Mono; } if (text.Contains("il2cpp")) { return Branch.Il2Cpp; } try { AssemblyDefinition val = AssemblyDefinition.ReadAssembly(path); try { Enumerator<ModuleDefinition> enumerator = val.Modules.GetEnumerator(); try { while (enumerator.MoveNext()) { foreach (TypeReference typeReference in enumerator.Current.GetTypeReferences()) { string text2 = (typeReference.Namespace ?? "").ToLowerInvariant(); if (text2.StartsWith("il2cppscheduleone") || text2.StartsWith("il2cppsystem")) { return Branch.Il2Cpp; } if (text2 == "scheduleone") { return Branch.Mono; } } } } finally { ((IDisposable)enumerator).Dispose(); } } finally { ((IDisposable)val)?.Dispose(); } } catch { } return null; } }