public static PluginInfo ToPluginInfo(TypeDefinition type) { if (type.IsInterface || type.IsAbstract) { return(null); } try { if (!type.IsSubtypeOf(typeof(BaseUnityPlugin))) { return(null); } } catch (AssemblyResolutionException) { // Can happen if this type inherits a type from an assembly that can't be found. Safe to assume it's not a plugin. return(null); } var metadata = BepInPlugin.FromCecilType(type); // Perform checks that will prevent the plugin from being loaded in ALL cases if (metadata == null) { Logger.LogWarning($"Skipping over type [{type.FullName}] as no metadata attribute is specified"); return(null); } if (string.IsNullOrEmpty(metadata.GUID) || !allowedGuidRegex.IsMatch(metadata.GUID)) { Logger.LogWarning($"Skipping type [{type.FullName}] because its GUID [{metadata.GUID}] is of an illegal format."); return(null); } if (metadata.Version == null) { Logger.LogWarning($"Skipping type [{type.FullName}] because its version is invalid."); return(null); } if (metadata.Name == null) { Logger.LogWarning($"Skipping type [{type.FullName}] because its name is null."); return(null); } var filters = BepInProcess.FromCecilType(type); var dependencies = BepInDependency.FromCecilType(type); var incompatibilities = BepInIncompatibility.FromCecilType(type); return(new PluginInfo { Metadata = metadata, Processes = filters, Dependencies = dependencies, Incompatibilities = incompatibilities, TypeName = type.FullName }); }
private void OnInstallUnityTlsInterface(IntPtr unityTlsInterfaceStruct) { Logger.LogDebug($"Captured UnityTls interface at {unityTlsInterfaceStruct.ToInt64():x8}"); Il2CppTlsAdapter.Options.UnityTlsInterface = unityTlsInterfaceStruct; originalInstallUnityTlsInterface(unityTlsInterfaceStruct); InstallUnityTlsInterfaceDetour.Dispose(); InstallUnityTlsInterfaceDetour = null; }
void Update() { if (Refresh.Value.IsDown()) { Logger.LogInfo("Refreshing!"); LoadSongs(); } }
/// <summary> /// Initializes BepInEx to be able to start the chainloader. /// </summary> public static void Initialize(string gameExePath, bool startConsole = true) { if (_initialized) { return; } ThreadingHelper.Initialize(); // Set vitals if (gameExePath != null) { // Checking for null allows a more advanced initialization workflow, where the Paths class has been initialized before calling Chainloader.Initialize // This is used by Preloader to use environment variables, for example Paths.SetExecutablePath(gameExePath); } // Start logging if (ConsoleWindow.ConfigConsoleEnabled.Value && startConsole) { ConsoleWindow.Attach(); Logger.Listeners.Add(new ConsoleLogListener()); } // Fix for standard output getting overwritten by UnityLogger if (ConsoleWindow.StandardOut != null) { Console.SetOut(ConsoleWindow.StandardOut); var encoding = ConsoleWindow.ConfigConsoleShiftJis.Value ? 932 : (uint)Encoding.UTF8.CodePage; ConsoleEncoding.ConsoleCodePage = encoding; Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding); } Logger.Listeners.Add(new UnityLogListener()); if (ConfigDiskLogging.Value) { Logger.Listeners.Add(new DiskLogListener("LogOutput.log", ConfigDiskConsoleDisplayedLevel.Value, ConfigDiskAppend.Value, ConfigDiskWriteUnityLog.Value)); } if (!TraceLogSource.IsListening) { Logger.Sources.Add(TraceLogSource.CreateSource()); } if (ConfigUnityLogging.Value) { Logger.Sources.Add(new UnityLogSource()); } Logger.LogMessage("Chainloader ready"); _initialized = true; }
/// <summary> /// Initializes BepInEx to be able to start the chainloader. /// </summary> public static void Initialize(string gameExePath, bool startConsole = true, ICollection <LogEventArgs> preloaderLogEvents = null) { if (_initialized) { return; } ThreadingHelper.Initialize(); // Set vitals if (gameExePath != null) { // Checking for null allows a more advanced initialization workflow, where the Paths class has been initialized before calling Chainloader.Initialize // This is used by Preloader to use environment variables, for example Paths.SetExecutablePath(gameExePath); } // Start logging if (ConsoleManager.ConfigConsoleEnabled.Value && startConsole) { ConsoleManager.CreateConsole(); Logger.Listeners.Add(new ConsoleLogListener()); } Logger.InitializeInternalLoggers(); if (ConfigDiskLogging.Value) { Logger.Listeners.Add(new DiskLogListener("LogOutput.log", ConfigDiskConsoleDisplayedLevel.Value, ConfigDiskAppend.Value, ConfigDiskWriteUnityLog.Value)); } if (!TraceLogSource.IsListening) { Logger.Sources.Add(TraceLogSource.CreateSource()); } ReplayPreloaderLogs(preloaderLogEvents); // Add Unity log source only after replaying to prevent duplication in console if (ConfigUnityLogging.Value) { Logger.Sources.Add(new UnityLogSource()); } Logger.Listeners.Add(new UnityLogListener()); if (Utility.CurrentOs == Platform.Linux) { Logger.LogInfo($"Detected Unity version: v{Application.unityVersion}"); } Logger.LogMessage("Chainloader ready"); _initialized = true; }
void Awake() { CustomBeatsPlugin.INSTANCE = this; CustomBeatsPlugin.LOGGER = this.Logger; Logger.LogInfo("Helloooooooo, world!"); Logger.LogInfo(Application.streamingAssetsPath); harmony = Harmony.CreateAndPatchAll(typeof(Patches)); LoadSongs(); }
/// <summary> /// Attempt to deserialize key combination from the string. /// </summary> public static KeyboardShortcut Deserialize(string str) { try { var parts = str.Split(new[] { ' ', '+', ',', ';', '|' }, StringSplitOptions.RemoveEmptyEntries) .Select(x => (KeyCode)Enum.Parse(typeof(KeyCode), x)).ToArray(); return(new KeyboardShortcut(parts)); } catch (SystemException ex) { Logger.Log(LogLevel.Error, "Failed to read keybind from settings: " + ex.Message); return(Empty); } }
/// <summary> /// Initializes BepInEx to be able to start the chainloader. /// </summary> public static void Initialize(string containerExePath, bool startConsole = true) { if (_initialized) { return; } //Set vitals Paths.SetExecutablePath(containerExePath); Paths.SetPluginPath(ConfigPluginsDirectory.Value); //Start logging if (startConsole) { ConsoleWindow.Attach(); ConsoleEncoding.ConsoleCodePage = (uint)Encoding.UTF8.CodePage; Console.OutputEncoding = Encoding.UTF8; Logger.Listeners.Add(new ConsoleLogListener()); } //Fix for standard output getting overwritten by UnityLogger if (ConsoleWindow.StandardOut != null) { Console.SetOut(ConsoleWindow.StandardOut); } Logger.Listeners.Add(new UnityLogListener()); Logger.Listeners.Add(new DiskLogListener()); if (!TraceLogSource.IsListening) { Logger.Sources.Add(TraceLogSource.CreateSource()); } if (ConfigUnityLogging.Value) { Logger.Sources.Add(new UnityLogSource()); } Logger.LogMessage("Chainloader ready"); _initialized = true; }
public override unsafe void Initialize(string gameExePath = null) { UnhollowerBaseLib.GeneratedDatabasesUtil.DatabasesLocationOverride = Preloader.IL2CPPUnhollowedPath; PatchManager.ResolvePatcher += IL2CPPDetourMethodPatcher.TryResolve; base.Initialize(gameExePath); Instance = this; var version = //Version.Parse(Application.unityVersion); Version.Parse(Process.GetCurrentProcess().MainModule.FileVersionInfo.FileVersion); UnityVersionHandler.Initialize(version.Major, version.Minor, version.Revision); // One or the other here for Unhollower to work correctly //ClassInjector.Detour = new DetourHandler(); ClassInjector.DoHook = (ptr, patchedFunctionPtr) => { IntPtr originalFunc = new IntPtr(*(void **)ptr); var detour = new FastNativeDetour(originalFunc, patchedFunctionPtr); detour.Apply(); *(void **)ptr = (void *)detour.TrampolinePtr; }; var gameAssemblyModule = Process.GetCurrentProcess().Modules.Cast <ProcessModule>().First(x => x.ModuleName.Contains("GameAssembly")); // TODO: Check that DynDll.GetFunction works fine now var runtimeInvokePtr = GetProcAddress(gameAssemblyModule.BaseAddress, "il2cpp_runtime_invoke"); //DynDll.GetFunction(gameAssemblyModule.BaseAddress, "il2cpp_runtime_invoke"); PreloaderLogger.Log.LogDebug($"Runtime invoke pointer: 0x{runtimeInvokePtr.ToInt64():X}"); RuntimeInvokeDetour = FastNativeDetour.CreateAndApply(runtimeInvokePtr, OnInvokeMethod, out originalInvoke, CallingConvention.Cdecl); var installTlsPtr = GetProcAddress(gameAssemblyModule.BaseAddress, "il2cpp_unity_install_unitytls_interface"); if (installTlsPtr != IntPtr.Zero) { InstallUnityTlsInterfaceDetour = FastNativeDetour.CreateAndApply(installTlsPtr, OnInstallUnityTlsInterface, out originalInstallUnityTlsInterface, CallingConvention.Cdecl); } Logger.LogDebug("Initializing TLS adapters"); Il2CppTlsAdapter.Initialize(); PreloaderLogger.Log.LogDebug("Runtime invoke patched"); }
public static PluginInfo ToPluginInfo(TypeDefinition type) { if (type.IsInterface || type.IsAbstract || !type.IsSubtypeOf(typeof(BaseUnityPlugin))) { return(null); } var metadata = BepInPlugin.FromCecilType(type); // Perform checks that will prevent the plugin from being loaded in ALL cases if (metadata == null) { Logger.LogWarning($"Skipping over type [{type.FullName}] as no metadata attribute is specified"); return(null); } if (string.IsNullOrEmpty(metadata.GUID) || !allowedGuidRegex.IsMatch(metadata.GUID)) { Logger.LogWarning($"Skipping type [{type.FullName}] because its GUID [{metadata.GUID}] is of an illegal format."); return(null); } if (metadata.Version == null) { Logger.LogWarning($"Skipping type [{type.FullName}] because its version is invalid."); return(null); } if (metadata.Name == null) { Logger.LogWarning($"Skipping type [{type.FullName}] because its name is null."); return(null); } var filters = BepInProcess.FromCecilType(type); var dependencies = BepInDependency.FromCecilType(type); return(new PluginInfo { Metadata = metadata, Processes = filters, Dependencies = dependencies, TypeName = type.FullName }); }
/// <summary> /// Initialize Registry with needed data /// </summary> /// <param name="bundleName">Name of bundle to load</param> /// <param name="keyword">UNIQUE keyword of your mod</param> /// <param name="requireBundle">Do you need to load asset bundles</param> /// <param name="requireVerta">Do you need to load verta files</param> public static void Init(string bundleName, string keyword, bool requireBundle, bool requireVerta) { LogSource = Logger.CreateLogSource("Registry-" + keyword); keyWord = keyword; //get location of the plugin string pluginfolder = Path.GetDirectoryName(Assembly.GetAssembly(typeof(Registry)).Location); int MainTex = Shader.PropertyToID("_MainTex"); int NormalTex = Shader.PropertyToID("_NormalTex"); int MSTex = Shader.PropertyToID("_MS_Tex"); int EmissionTex = Shader.PropertyToID("_EmissionTex"); textureNames = new[] { MainTex, NormalTex, MSTex, EmissionTex }; FileInfo folder = new FileInfo($"{pluginfolder}/Verta/"); FileInfo folder1 = new FileInfo($"{pluginfolder}/plugins/"); if (Directory.Exists(folder.Directory?.FullName)) { vertaFolder = pluginfolder; } else if (Directory.Exists(folder1.Directory?.FullName)) { vertaFolder = $"{pluginfolder}/plugins"; } else if (requireVerta) { vertaFolder = ""; LogSource.LogError("Cannot find folder with verta files. Mod WILL not work!"); return; } if (requireBundle) { //load assetbundle then load the prefab bundle = AssetBundle.LoadFromFile($"{pluginfolder}/{bundleName}"); } LDBTool.PostAddDataAction += onPostAdd; LDBTool.EditDataAction += EditProto; Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly()); }
public override void Initialize(string gameExePath = null) { GeneratedDatabasesUtil.DatabasesLocationOverride = Preloader.IL2CPPUnhollowedPath; PatchManager.ResolvePatcher += IL2CPPDetourMethodPatcher.TryResolve; base.Initialize(gameExePath); Instance = this; ClassInjector.Detour = new UnhollowerDetourHandler(); var gameAssemblyModule = Process.GetCurrentProcess().Modules.Cast <ProcessModule>() .FirstOrDefault(x => x.ModuleName.Contains("GameAssembly") || x.ModuleName.Contains("UserAssembly")); if (gameAssemblyModule == null) { Logger.Log(LogLevel.Fatal, "Could not locate Il2Cpp game assembly (GameAssembly.dll) or (UserAssembly.dll). The game might be obfuscated or use a yet unsupported build of Unity."); return; } gameAssemblyModule.BaseAddress.TryGetFunction("il2cpp_runtime_invoke", out var runtimeInvokePtr); PreloaderLogger.Log.Log(LogLevel.Debug, $"Runtime invoke pointer: 0x{runtimeInvokePtr.ToInt64():X}"); RuntimeInvokeDetour = FastNativeDetour.CreateAndApply(runtimeInvokePtr, OnInvokeMethod, out originalInvoke, CallingConvention.Cdecl); if (gameAssemblyModule.BaseAddress.TryGetFunction("il2cpp_unity_install_unitytls_interface", out var installTlsPtr)) { InstallUnityTlsInterfaceDetour = FastNativeDetour.CreateAndApply(installTlsPtr, OnInstallUnityTlsInterface, out originalInstallUnityTlsInterface, CallingConvention.Cdecl); } Logger.Log(LogLevel.Debug, "Initializing TLS adapters"); Il2CppTlsAdapter.Initialize(); PreloaderLogger.Log.Log(LogLevel.Debug, "Runtime invoke patched"); }
private static IntPtr OnInvokeMethod(IntPtr method, IntPtr obj, IntPtr parameters, IntPtr exc) { var methodName = Marshal.PtrToStringAnsi(UnhollowerBaseLib.IL2CPP.il2cpp_method_get_name(method)); var unhook = false; if (methodName == "Internal_ActiveSceneChanged") { try { if (ConfigUnityLogging.Value) { Logger.Sources.Add(new IL2CPPUnityLogSource()); Application.CallLogCallback("Test call after applying unity logging hook", "", LogType.Assert, true); } unhook = true; Instance.Execute(); } catch (Exception ex) { Logger.LogFatal("Unable to execute IL2CPP chainloader"); Logger.LogError(ex); } } var result = originalInvoke(method, obj, parameters, exc); if (unhook) { RuntimeInvokeDetour.Dispose(); PreloaderLogger.Log.LogDebug("Runtime invoke unpatched"); } return(result); }
public RichPresence(IntPtr intPtr) : base(intPtr) { log = Logger.CreateLogSource("Discord Rich Presence"); _discord = new Discord.Discord(764433332330037249L, 0UL); _activityManager = _discord.GetActivityManager(); // TODO: ? _activityManager.RegisterCommand("gtfo://run"); _activityManager.RegisterSteam(493520); _currentTime = _currentTime.AddSeconds(10); _activityManager.OnActivityJoin += Events.OnActivityJoin; log.LogMessage("RichPresence created."); Activity activity = new Activity() { State = "Playing GTFO", Details = "Selecting an expedition." }; SetActivity(activity); }
static UnityLogListener() { foreach (var methodInfo in typeof(UnityLogWriter).GetMethods(BindingFlags.Static | BindingFlags.Public)) { try { methodInfo.Invoke(null, new object[] { "" }); } catch { continue; } WriteStringToUnityLog = (Action <string>)Delegate.CreateDelegate(typeof(Action <string>), methodInfo); break; } if (WriteStringToUnityLog == null) { Logger.LogError("Unable to start Unity log writer"); } }
public override void Initialize(string gameExePath = null) { GeneratedDatabasesUtil.DatabasesLocationOverride = Preloader.IL2CPPUnhollowedPath; PatchManager.ResolvePatcher += IL2CPPDetourMethodPatcher.TryResolve; base.Initialize(gameExePath); Instance = this; ClassInjector.Detour = new UnhollowerDetourHandler(); var gameAssemblyModule = Process.GetCurrentProcess().Modules.Cast <ProcessModule>() .First(x => x.ModuleName.Contains("GameAssembly")); // TODO: Check that DynDll.GetFunction works fine now var runtimeInvokePtr = GetProcAddress(gameAssemblyModule.BaseAddress, "il2cpp_runtime_invoke"); //DynDll.GetFunction(gameAssemblyModule.BaseAddress, "il2cpp_runtime_invoke"); PreloaderLogger.Log.LogDebug($"Runtime invoke pointer: 0x{runtimeInvokePtr.ToInt64():X}"); RuntimeInvokeDetour = FastNativeDetour.CreateAndApply(runtimeInvokePtr, OnInvokeMethod, out originalInvoke, CallingConvention.Cdecl); var installTlsPtr = GetProcAddress(gameAssemblyModule.BaseAddress, "il2cpp_unity_install_unitytls_interface"); if (installTlsPtr != IntPtr.Zero) { InstallUnityTlsInterfaceDetour = FastNativeDetour.CreateAndApply(installTlsPtr, OnInstallUnityTlsInterface, out originalInstallUnityTlsInterface, CallingConvention.Cdecl); } Logger.LogDebug("Initializing TLS adapters"); Il2CppTlsAdapter.Initialize(); PreloaderLogger.Log.LogDebug("Runtime invoke patched"); }
private static void ReplayPreloaderLogs(ICollection <LogEventArgs> preloaderLogEvents) { if (preloaderLogEvents == null) { return; } var unityLogger = new UnityLogListener(); Logger.Listeners.Add(unityLogger); // Temporarily disable the console log listener (if there is one from preloader) as we replay the preloader logs var logListener = Logger.Listeners.FirstOrDefault(logger => logger is ConsoleLogListener); if (logListener != null) { Logger.Listeners.Remove(logListener); } // Write preloader log events if there are any, including the original log source name var preloaderLogSource = Logger.CreateLogSource("Preloader"); foreach (var preloaderLogEvent in preloaderLogEvents) { Logger.InternalLogEvent(preloaderLogSource, preloaderLogEvent); } Logger.Sources.Remove(preloaderLogSource); Logger.Listeners.Remove(unityLogger); if (logListener != null) { Logger.Listeners.Add(logListener); } }
/// <summary> /// The entrypoint for the BepInEx plugin system. /// </summary> public static void Start() { if (_loaded) { return; } if (!_initialized) { throw new InvalidOperationException("BepInEx has not been initialized. Please call Chainloader.Initialize prior to starting the chainloader instance."); } if (!Directory.Exists(Paths.PluginPath)) { Directory.CreateDirectory(Paths.PluginPath); } if (!Directory.Exists(Paths.PatcherPluginPath)) { Directory.CreateDirectory(Paths.PatcherPluginPath); } try { var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static); if (ConsoleManager.ConsoleActive) { ConsoleManager.SetConsoleTitle($"{CurrentAssemblyName} {CurrentAssemblyVersion} - {productNameProp?.GetValue(null, null) ?? Paths.ProcessName}"); } Logger.LogMessage("Chainloader started"); ManagerObject = new GameObject("BepInEx_Manager"); UnityEngine.Object.DontDestroyOnLoad(ManagerObject); var pluginsToLoad = TypeLoader.FindPluginTypes(Paths.PluginPath, ToPluginInfo, HasBepinPlugins, "chainloader"); foreach (var keyValuePair in pluginsToLoad) { foreach (var pluginInfo in keyValuePair.Value) { pluginInfo.Location = keyValuePair.Key; } } var pluginInfos = pluginsToLoad.SelectMany(p => p.Value).ToList(); var loadedAssemblies = new Dictionary <string, Assembly>(); Logger.LogInfo($"{pluginInfos.Count} plugins to load"); // We use a sorted dictionary to ensure consistent load order var dependencyDict = new SortedDictionary <string, IEnumerable <string> >(StringComparer.InvariantCultureIgnoreCase); var pluginsByGUID = new Dictionary <string, PluginInfo>(); foreach (var pluginInfoGroup in pluginInfos.GroupBy(info => info.Metadata.GUID)) { PluginInfo loadedVersion = null; foreach (var pluginInfo in pluginInfoGroup.OrderByDescending(x => x.Metadata.Version)) { if (loadedVersion != null) { Logger.LogWarning($"Skipping [{pluginInfo}] because a newer version exists ({loadedVersion})"); continue; } loadedVersion = pluginInfo; // Perform checks that will prevent loading plugins in this run var filters = pluginInfo.Processes.ToList(); bool invalidProcessName = filters.Count != 0 && filters.All(x => !string.Equals(x.ProcessName.Replace(".exe", ""), Paths.ProcessName, StringComparison.InvariantCultureIgnoreCase)); if (invalidProcessName) { Logger.LogWarning($"Skipping [{pluginInfo}] because of process filters ({string.Join(", ", pluginInfo.Processes.Select(p => p.ProcessName).ToArray())})"); continue; } dependencyDict[pluginInfo.Metadata.GUID] = pluginInfo.Dependencies.Select(d => d.DependencyGUID); pluginsByGUID[pluginInfo.Metadata.GUID] = pluginInfo; } } foreach (var pluginInfo in pluginsByGUID.Values.ToList()) { if (pluginInfo.Incompatibilities.Any(incompatibility => pluginsByGUID.ContainsKey(incompatibility.IncompatibilityGUID))) { pluginsByGUID.Remove(pluginInfo.Metadata.GUID); dependencyDict.Remove(pluginInfo.Metadata.GUID); var incompatiblePlugins = pluginInfo.Incompatibilities.Select(x => x.IncompatibilityGUID).Where(x => pluginsByGUID.ContainsKey(x)).ToArray(); string message = $@"Could not load [{pluginInfo}] because it is incompatible with: {string.Join(", ", incompatiblePlugins)}"; DependencyErrors.Add(message); Logger.LogError(message); } else if (PluginTargetsWrongBepin(pluginInfo)) { string message = $@"Plugin [{pluginInfo}] targets a wrong version of BepInEx ({pluginInfo.TargettedBepInExVersion}) and might not work until you update"; DependencyErrors.Add(message); Logger.LogWarning(message); } } var emptyDependencies = new string[0]; // Sort plugins by their dependencies. // Give missing dependencies no dependencies of its own, which will cause missing plugins to be first in the resulting list. var sortedPlugins = Utility.TopologicalSort(dependencyDict.Keys, x => dependencyDict.TryGetValue(x, out var deps) ? deps : emptyDependencies).ToList(); var invalidPlugins = new HashSet <string>(); var processedPlugins = new Dictionary <string, Version>(); foreach (var pluginGUID in sortedPlugins) { // If the plugin is missing, don't process it if (!pluginsByGUID.TryGetValue(pluginGUID, out var pluginInfo)) { continue; } var dependsOnInvalidPlugin = false; var missingDependencies = new List <BepInDependency>(); foreach (var dependency in pluginInfo.Dependencies) { bool IsHardDependency(BepInDependency dep) => (dep.Flags & BepInDependency.DependencyFlags.HardDependency) != 0; // If the dependency wasn't already processed, it's missing altogether bool dependencyExists = processedPlugins.TryGetValue(dependency.DependencyGUID, out var pluginVersion); if (!dependencyExists || pluginVersion < dependency.MinimumVersion) { // If the dependency is hard, collect it into a list to show if (IsHardDependency(dependency)) { missingDependencies.Add(dependency); } continue; } // If the dependency is invalid (e.g. has missing dependencies) and hard, report that to the user if (invalidPlugins.Contains(dependency.DependencyGUID) && IsHardDependency(dependency)) { dependsOnInvalidPlugin = true; break; } } processedPlugins.Add(pluginGUID, pluginInfo.Metadata.Version); if (dependsOnInvalidPlugin) { string message = $"Skipping [{pluginInfo}] because it has a dependency that was not loaded. See previous errors for details."; DependencyErrors.Add(message); Logger.LogWarning(message); continue; } if (missingDependencies.Count != 0) { bool IsEmptyVersion(Version v) => v.Major == 0 && v.Minor == 0 && v.Build <= 0 && v.Revision <= 0; string message = $@"Could not load [{pluginInfo}] because it has missing dependencies: { string.Join(", ", missingDependencies.Select(s => IsEmptyVersion(s.MinimumVersion) ? s.DependencyGUID : $"{s.DependencyGUID} (v{s.MinimumVersion} or newer)").ToArray()) }" ; DependencyErrors.Add(message); Logger.LogError(message); invalidPlugins.Add(pluginGUID); continue; } try { Logger.LogInfo($"Loading [{pluginInfo}]"); if (!loadedAssemblies.TryGetValue(pluginInfo.Location, out var ass)) { loadedAssemblies[pluginInfo.Location] = ass = Assembly.LoadFile(pluginInfo.Location); } PluginInfos[pluginGUID] = pluginInfo; pluginInfo.Instance = (BaseUnityPlugin)ManagerObject.AddComponent(ass.GetType(pluginInfo.TypeName)); _plugins.Add(pluginInfo.Instance); } catch (Exception ex) { invalidPlugins.Add(pluginGUID); PluginInfos.Remove(pluginGUID); Logger.LogError($"Error loading [{pluginInfo}] : {ex.Message}"); if (ex is ReflectionTypeLoadException re) { Logger.LogDebug(TypeLoader.TypeLoadExceptionToString(re)); } else { Logger.LogDebug(ex); } } } }
/// <summary> /// The entrypoint for the BepInEx plugin system. /// </summary> public static void Start() { if (_loaded) { return; } if (!_initialized) { throw new InvalidOperationException("BepInEx has not been initialized. Please call Chainloader.Initialize prior to starting the chainloader instance."); } if (!Directory.Exists(Paths.PluginPath)) { Directory.CreateDirectory(Paths.PluginPath); } if (!Directory.Exists(Paths.PatcherPluginPath)) { Directory.CreateDirectory(Paths.PatcherPluginPath); } try { var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static); if (productNameProp != null) { ConsoleWindow.Title = $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}"; } Logger.LogMessage("Chainloader started"); ManagerObject = new GameObject("BepInEx_Manager"); UnityEngine.Object.DontDestroyOnLoad(ManagerObject); string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower(); var globalPluginTypes = TypeLoader.LoadTypes <BaseUnityPlugin>(Paths.PluginPath).ToList(); var selectedPluginTypes = globalPluginTypes .Where(plugin => { //Ensure metadata exists var metadata = MetadataHelper.GetMetadata(plugin); if (metadata == null) { Logger.LogWarning($"Skipping over type [{plugin.Name}] as no metadata attribute is specified"); return(false); } //Perform a filter for currently running process var filters = MetadataHelper.GetAttributes <BepInProcess>(plugin); if (filters.Length == 0) //no filters means it loads everywhere { return(true); } var result = filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess); if (!result) { Logger.LogInfo($"Skipping over plugin [{metadata.GUID}] due to process filter"); } return(result); }) .ToList(); Logger.LogInfo($"{selectedPluginTypes.Count} / {globalPluginTypes.Count} plugins to load"); Dictionary <Type, IEnumerable <Type> > dependencyDict = new Dictionary <Type, IEnumerable <Type> >(); foreach (Type t in selectedPluginTypes) { try { IEnumerable <Type> dependencies = MetadataHelper.GetDependencies(t, selectedPluginTypes); dependencyDict[t] = dependencies; } catch (MissingDependencyException) { var metadata = MetadataHelper.GetMetadata(t); Logger.LogWarning($"Cannot load [{metadata.Name}] due to missing dependencies."); } } var sortedTypes = Utility.TopologicalSort(dependencyDict.Keys, x => dependencyDict[x]).ToList(); foreach (Type t in sortedTypes) { try { var metadata = MetadataHelper.GetMetadata(t); Logger.LogInfo($"Loading [{metadata.Name} {metadata.Version}]"); var plugin = (BaseUnityPlugin)ManagerObject.AddComponent(t); Plugins.Add(plugin); } catch (Exception ex) { Logger.LogError($"Error loading [{t.Name}] : {ex.Message}"); Logger.LogDebug(ex); } } } catch (Exception ex) { ConsoleWindow.Attach(); Console.WriteLine("Error occurred starting the game"); Console.WriteLine(ex.ToString()); } Logger.LogMessage("Chainloader startup complete"); _loaded = true; }
/// <summary> /// The entrypoint for the BepInEx plugin system. /// </summary> public static void Start() { if (_loaded) { return; } if (!_initialized) { throw new InvalidOperationException("BepInEx has not been initialized. Please call Chainloader.Initialize prior to starting the chainloader instance."); } if (!Directory.Exists(Paths.PluginPath)) { Directory.CreateDirectory(Paths.PluginPath); } if (!Directory.Exists(Paths.PatcherPluginPath)) { Directory.CreateDirectory(Paths.PatcherPluginPath); } try { var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static); if (productNameProp != null) { ConsoleWindow.Title = $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}"; } Logger.LogMessage("Chainloader started"); ManagerObject = new GameObject("BepInEx_Manager"); UnityEngine.Object.DontDestroyOnLoad(ManagerObject); string currentProcess = Process.GetCurrentProcess().ProcessName.ToLower(); var globalPluginTypes = TypeLoader.LoadTypes <BaseUnityPlugin>(Paths.PluginPath).ToList(); var selectedPluginTypes = globalPluginTypes .Where(plugin => { //Ensure metadata exists var metadata = MetadataHelper.GetMetadata(plugin); if (metadata == null) { Logger.LogWarning($"Skipping over type [{plugin.Name}] as no metadata attribute is specified"); return(false); } //Perform a filter for currently running process var filters = MetadataHelper.GetAttributes <BepInProcess>(plugin); if (filters.Length == 0) //no filters means it loads everywhere { return(true); } var result = filters.Any(x => x.ProcessName.ToLower().Replace(".exe", "") == currentProcess); if (!result) { Logger.LogInfo($"Skipping over plugin [{metadata.GUID}] due to process filter"); } return(result); }) .ToList(); Logger.LogInfo($"{selectedPluginTypes.Count} / {globalPluginTypes.Count} plugins to load"); var dependencyDict = new Dictionary <string, IEnumerable <string> >(); var pluginsByGUID = new Dictionary <string, Type>(); foreach (Type t in selectedPluginTypes) { var dependencies = MetadataHelper.GetDependencies(t, selectedPluginTypes); var metadata = MetadataHelper.GetMetadata(t); if (metadata.GUID == null) { Logger.LogWarning($"Skipping [{metadata.Name}] because it does not have a valid GUID."); continue; } if (dependencyDict.ContainsKey(metadata.GUID)) { Logger.LogWarning($"Skipping [{metadata.Name}] because its GUID ({metadata.GUID}) is already used by another plugin."); continue; } dependencyDict[metadata.GUID] = dependencies.Select(d => d.DependencyGUID); pluginsByGUID[metadata.GUID] = t; } var emptyDependencies = new string[0]; // Sort plugins by their dependencies. // Give missing dependencies no dependencies of its own, which will cause missing plugins to be first in the resulting list. var sortedPlugins = Utility.TopologicalSort(dependencyDict.Keys, x => dependencyDict.TryGetValue(x, out var deps) ? deps : emptyDependencies).ToList(); var invalidPlugins = new HashSet <string>(); var processedPlugins = new HashSet <string>(); foreach (var pluginGUID in sortedPlugins) { // If the plugin is missing, don't process it if (!pluginsByGUID.TryGetValue(pluginGUID, out var pluginType)) { continue; } var metadata = MetadataHelper.GetMetadata(pluginType); var dependencies = MetadataHelper.GetDependencies(pluginType, selectedPluginTypes); var dependsOnInvalidPlugin = false; var missingDependencies = new List <string>(); foreach (var dependency in dependencies) { // If the depenency wasn't already processed, it's missing altogether if (!processedPlugins.Contains(dependency.DependencyGUID)) { // If the dependency is hard, collect it into a list to show if ((dependency.Flags & BepInDependency.DependencyFlags.HardDependency) != 0) { missingDependencies.Add(dependency.DependencyGUID); } continue; } // If the dependency is invalid (e.g. has missing depedencies), report that to the user if (invalidPlugins.Contains(dependency.DependencyGUID)) { dependsOnInvalidPlugin = true; break; } } processedPlugins.Add(pluginGUID); if (dependsOnInvalidPlugin) { Logger.LogWarning($"Skipping [{metadata.Name}] because it has a dependency that was not loaded. See above errors for details."); continue; } if (missingDependencies.Count != 0) { Logger.LogError($@"Missing the following dependencies for [{metadata.Name}]: {"\r\n"}{ string.Join("\r\n", missingDependencies.Select(s => $"- {s}").ToArray()) }{"\r\n"}Loading will be skipped; expect further errors and unstabilities." ); invalidPlugins.Add(pluginGUID); continue; } try { Logger.LogInfo($"Loading [{metadata.Name} {metadata.Version}]"); Plugins.Add((BaseUnityPlugin)ManagerObject.AddComponent(pluginType)); } catch (Exception ex) { invalidPlugins.Add(pluginGUID); Logger.LogError($"Error loading [{metadata.Name}] : {ex.Message}"); Logger.LogDebug(ex); } } } catch (Exception ex) { ConsoleWindow.Attach(); Console.WriteLine("Error occurred starting the game"); Console.WriteLine(ex.ToString()); } Logger.LogMessage("Chainloader startup complete"); _loaded = true; }
/// <summary> /// The entrypoint for the BepInEx plugin system. /// </summary> public static void Start() { if (_loaded) { return; } if (!_initialized) { throw new InvalidOperationException("BepInEx has not been initialized. Please call Chainloader.Initialize prior to starting the chainloader instance."); } if (!Directory.Exists(Paths.PluginPath)) { Directory.CreateDirectory(Paths.PluginPath); } if (!Directory.Exists(Paths.PatcherPluginPath)) { Directory.CreateDirectory(Paths.PatcherPluginPath); } try { var productNameProp = typeof(Application).GetProperty("productName", BindingFlags.Public | BindingFlags.Static); if (productNameProp != null) { ConsoleWindow.Title = $"BepInEx {Assembly.GetExecutingAssembly().GetName().Version} - {productNameProp.GetValue(null, null)}"; } Logger.LogMessage("Chainloader started"); ManagerObject = new GameObject("BepInEx_Manager"); UnityEngine.Object.DontDestroyOnLoad(ManagerObject); var pluginsToLoad = TypeLoader.FindPluginTypes(Paths.PluginPath, ToPluginInfo, HasBepinPlugins, "chainloader"); foreach (var keyValuePair in pluginsToLoad) { foreach (var pluginInfo in keyValuePair.Value) { pluginInfo.Location = keyValuePair.Key; } } var pluginInfos = pluginsToLoad.SelectMany(p => p.Value).ToList(); var loadedAssemblies = new Dictionary <string, Assembly>(); Logger.LogInfo($"{pluginInfos.Count} plugins to load"); var dependencyDict = new Dictionary <string, IEnumerable <string> >(); var pluginsByGUID = new Dictionary <string, PluginInfo>(); foreach (var pluginInfo in pluginInfos) { // Perform checks that will prevent loading plugins in this run var filters = pluginInfo.Processes.ToList(); bool invalidProcessName = filters.Count != 0 && filters.All(x => !string.Equals(x.ProcessName.Replace(".exe", ""), Paths.ProcessName, StringComparison.InvariantCultureIgnoreCase)); if (invalidProcessName) { Logger.LogWarning($"Skipping over plugin [{pluginInfo.Metadata.GUID}] due to process filter"); continue; } if (dependencyDict.ContainsKey(pluginInfo.Metadata.GUID)) { Logger.LogWarning($"Skipping [{pluginInfo.Metadata.Name}] because its GUID ({pluginInfo.Metadata.GUID}) is already used by another plugin."); continue; } dependencyDict[pluginInfo.Metadata.GUID] = pluginInfo.Dependencies.Select(d => d.DependencyGUID).Concat(pluginInfo.Incompatibilities.Select(i => i.IncompatibilityGUID)); pluginsByGUID[pluginInfo.Metadata.GUID] = pluginInfo; } var emptyDependencies = new string[0]; // Sort plugins by their dependencies. // Give missing dependencies no dependencies of its own, which will cause missing plugins to be first in the resulting list. var sortedPlugins = Utility.TopologicalSort(dependencyDict.Keys, x => dependencyDict.TryGetValue(x, out var deps) ? deps : emptyDependencies).ToList(); var invalidPlugins = new HashSet <string>(); var processedPlugins = new Dictionary <string, Version>(); foreach (var pluginGUID in sortedPlugins) { // If the plugin is missing, don't process it if (!pluginsByGUID.TryGetValue(pluginGUID, out var pluginInfo)) { continue; } var dependsOnInvalidPlugin = false; var missingDependencies = new List <BepInDependency>(); foreach (var dependency in pluginInfo.Dependencies) { // If the depenency wasn't already processed, it's missing altogether bool depenencyExists = processedPlugins.TryGetValue(dependency.DependencyGUID, out var pluginVersion); if (!depenencyExists || pluginVersion < dependency.MinimumVersion) { // If the dependency is hard, collect it into a list to show if ((dependency.Flags & BepInDependency.DependencyFlags.HardDependency) != 0) { missingDependencies.Add(dependency); } continue; } // If the dependency is invalid (e.g. has missing depedencies), report that to the user if (invalidPlugins.Contains(dependency.DependencyGUID)) { dependsOnInvalidPlugin = true; break; } } var incompatibilities = new List <BepInIncompatibility>(); foreach (var incompatibility in pluginInfo.Incompatibilities) { if (processedPlugins.ContainsKey(incompatibility.IncompatibilityGUID)) { incompatibilities.Add(incompatibility); } } processedPlugins.Add(pluginGUID, pluginInfo.Metadata.Version); if (dependsOnInvalidPlugin) { string message = $"Skipping [{pluginInfo.Metadata.Name}] because it has a dependency that was not loaded. See above errors for details."; DependencyErrors.Add(message); Logger.LogWarning(message); continue; } if (missingDependencies.Count != 0) { string ToMissingString(BepInDependency s) { bool emptyVersion = s.MinimumVersion.Major == 0 && s.MinimumVersion.Minor == 0 && s.MinimumVersion.Build == 0 && s.MinimumVersion.Revision == 0; if (emptyVersion) { return("- " + s.DependencyGUID); } return($"- {s.DependencyGUID} (at least v{s.MinimumVersion})"); } string message = $@"Could not load [{pluginInfo.Metadata.Name}] because it has missing dependencies: {string.Join(", ", missingDependencies.Select(ToMissingString).ToArray())}"; DependencyErrors.Add(message); Logger.LogError(message); invalidPlugins.Add(pluginGUID); continue; } if (incompatibilities.Count != 0) { string message = $@"Could not load [{pluginInfo.Metadata.Name}] because it is incompatible with: {string.Join(", ", incompatibilities.Select(i => i.IncompatibilityGUID).ToArray())}"; DependencyErrors.Add(message); Logger.LogError(message); invalidPlugins.Add(pluginGUID); continue; } try { Logger.LogInfo($"Loading [{pluginInfo.Metadata.Name} {pluginInfo.Metadata.Version}]"); if (!loadedAssemblies.TryGetValue(pluginInfo.Location, out var ass)) { loadedAssemblies[pluginInfo.Location] = ass = Assembly.LoadFile(pluginInfo.Location); } PluginInfos[pluginGUID] = pluginInfo; pluginInfo.Instance = (BaseUnityPlugin)ManagerObject.AddComponent(ass.GetType(pluginInfo.TypeName)); Plugins.Add(pluginInfo.Instance); } catch (Exception ex) { invalidPlugins.Add(pluginGUID); PluginInfos.Remove(pluginGUID); Logger.LogError($"Error loading [{pluginInfo.Metadata.Name}] : {ex.Message}"); if (ex is ReflectionTypeLoadException re) { Logger.LogDebug(TypeLoader.TypeLoadExceptionToString(re)); } else { Logger.LogDebug(ex); } } } } catch (Exception ex) { ConsoleWindow.Attach(); Console.WriteLine("Error occurred starting the game"); Console.WriteLine(ex.ToString()); } Logger.LogMessage("Chainloader startup complete"); _loaded = true; }
/// <summary> /// Initializes BepInEx to be able to start the chainloader. /// </summary> public static void Initialize(string gameExePath, bool startConsole = true, ICollection <LogEventArgs> preloaderLogEvents = null) { if (_initialized) { return; } ThreadingHelper.Initialize(); // Set vitals if (gameExePath != null) { // Checking for null allows a more advanced initialization workflow, where the Paths class has been initialized before calling Chainloader.Initialize // This is used by Preloader to use environment variables, for example Paths.SetExecutablePath(gameExePath); } // Start logging if (ConsoleManager.ConfigConsoleEnabled.Value && startConsole) { ConsoleManager.CreateConsole(); Logger.Listeners.Add(new ConsoleLogListener()); } // Fix for standard output getting overwritten by UnityLogger if (ConsoleManager.ConsoleActive) { ConsoleManager.SetConsoleStreams(); ConsoleManager.SetConsoleEncoding(); } Logger.InitializeInternalLoggers(); Logger.Listeners.Add(new UnityLogListener()); if (ConfigDiskLogging.Value) { Logger.Listeners.Add(new DiskLogListener("LogOutput.log", ConfigDiskConsoleDisplayedLevel.Value, ConfigDiskAppend.Value, ConfigDiskWriteUnityLog.Value)); } if (!TraceLogSource.IsListening) { Logger.Sources.Add(TraceLogSource.CreateSource()); } if (ConfigUnityLogging.Value) { Logger.Sources.Add(new UnityLogSource()); } // Temporarily disable the console log listener as we replay the preloader logs var logListener = Logger.Listeners.FirstOrDefault(logger => logger is ConsoleLogListener); if (logListener != null) { Logger.Listeners.Remove(logListener); } // Write preloader log events if there are any, including the original log source name if (preloaderLogEvents != null) { var preloaderLogSource = Logger.CreateLogSource("Preloader"); foreach (var preloaderLogEvent in preloaderLogEvents) { Logger.InternalLogEvent(preloaderLogSource, preloaderLogEvent); } Logger.Sources.Remove(preloaderLogSource); } if (logListener != null) { Logger.Listeners.Add(logListener); } if (Utility.CurrentOs == Platform.Linux) { Logger.LogInfo($"Detected Unity version: v{Application.unityVersion}"); } Logger.LogMessage("Chainloader ready"); _initialized = true; }
/// <summary> /// Initializes BepInEx to be able to start the chainloader. /// </summary> public static void Initialize(string gameExePath, bool startConsole = true, ICollection <LogEventArgs> preloaderLogEvents = null) { if (_initialized) { return; } ThreadingHelper.Initialize(); // Set vitals if (gameExePath != null) { // Checking for null allows a more advanced initialization workflow, where the Paths class has been initialized before calling Chainloader.Initialize // This is used by Preloader to use environment variables, for example Paths.SetExecutablePath(gameExePath); } // Start logging if (ConsoleWindow.ConfigConsoleEnabled.Value && startConsole) { ConsoleWindow.Attach(); Logger.Listeners.Add(new ConsoleLogListener()); } // Fix for standard output getting overwritten by UnityLogger if (ConsoleWindow.StandardOut != null) { Console.SetOut(ConsoleWindow.StandardOut); var encoding = ConsoleWindow.ConfigConsoleShiftJis.Value ? 932 : (uint)Encoding.UTF8.CodePage; ConsoleEncoding.ConsoleCodePage = encoding; Console.OutputEncoding = ConsoleEncoding.GetEncoding(encoding); } Logger.Listeners.Add(new UnityLogListener()); if (ConfigDiskLogging.Value) { Logger.Listeners.Add(new DiskLogListener("LogOutput.log", ConfigDiskConsoleDisplayedLevel.Value, ConfigDiskAppend.Value, ConfigDiskWriteUnityLog.Value)); } if (!TraceLogSource.IsListening) { Logger.Sources.Add(TraceLogSource.CreateSource()); } if (ConfigUnityLogging.Value) { Logger.Sources.Add(new UnityLogSource()); } // Temporarily disable the console log listener as we replay the preloader logs var logListener = Logger.Listeners.FirstOrDefault(logger => logger is ConsoleLogListener); if (logListener != null) { Logger.Listeners.Remove(logListener); } // Write preloader log events if there are any, including the original log source name if (preloaderLogEvents != null) { var preloaderLogSource = Logger.CreateLogSource("Preloader"); foreach (var preloaderLogEvent in preloaderLogEvents) { preloaderLogSource.Log(preloaderLogEvent.Level, $"[{preloaderLogEvent.Source.SourceName,10}] {preloaderLogEvent.Data}"); } Logger.Sources.Remove(preloaderLogSource); } if (logListener != null) { Logger.Listeners.Add(logListener); } Logger.LogMessage("Chainloader ready"); _initialized = true; }
void LoadSongs() { songs = new List <string>(); beatmaps = new List <BeatmapInfo>(); string[] defaultDifficulties = new[] { "Beginner", "Easy", "Normal", "Hard", "UNBEATABLE", "Trailer", "Tutorial" }; difficulties = new List <string>(); string customSongDir = $"{Application.dataPath.Substring(0, Application.dataPath.LastIndexOf('/'))}/CustomBeats/Songs/"; if (!Directory.Exists(customSongDir)) { Directory.CreateDirectory(customSongDir); } Logger.LogInfo($"CustomBeats Song Directory: {customSongDir}"); string[] songDirs = Directory.GetDirectories(customSongDir); foreach (var dir in songDirs) { string songName = Path.GetFileNameWithoutExtension(dir); Logger.LogInfo("Loading song " + songName); bool hasMaps = false; string[] osuFiles = Directory.GetFiles(dir, "*.osu"); foreach (var osuFile in osuFiles) { string beatmapName = Path.GetFileNameWithoutExtension(osuFile); string content = File.ReadAllText(osuFile); TextAsset asset = new TextAsset(content); asset.name = beatmapName; var difficultyMatch = Regex.Match(content, "Version: *(.+?)\r?\n"); string difficulty = difficultyMatch.Groups[1].Value; if (!defaultDifficulties.Contains(difficulty) && !difficulties.Contains(difficulty)) { difficulties.Add(difficulty); } var titleMatch = Regex.Match(content, "Title: *(.+?)\r?\n"); string title = titleMatch.Groups[1].Value; var clipMatch = Regex.Match(content, "AudioFilename: *(.+?)\r?\n"); var clip = clipMatch.Groups[1].Value; Logger.LogInfo(" - Loading beatmap " + beatmapName + " (" + difficulty + "): " + clip); CustomBeatmapInfo beatmap = new CustomBeatmapInfo(asset, songName, difficulty, clip); beatmaps.Add(beatmap); hasMaps = true; } if (hasMaps) { songs.Add(songName); } } var allDifficulties = defaultDifficulties.Concat(difficulties).ToList(); beatmaps.Sort(delegate(BeatmapInfo x, BeatmapInfo y) { return(allDifficulties.IndexOf(x.difficulty) - allDifficulties.IndexOf(y.difficulty)); }); dirty = true; }
void OnDestroy() { Logger.LogInfo("Goodbye, world!"); harmony.UnpatchSelf(); }