using System; using System.Collections; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Security; using System.Security.Cryptography; using System.Security.Permissions; using System.Text; using System.Text.RegularExpressions; using System.Threading; using BepInEx; using BepInEx.Bootstrap; using BepInEx.Configuration; using BepInEx.ConsoleUtil; using BepInEx.Core.Logging.Interpolation; using BepInEx.Logging; using BepInEx.Unix; using HarmonyLib; using HarmonyLib.Tools; using Microsoft.Win32.SafeHandles; using Mono.Cecil; using Mono.Collections.Generic; using MonoMod.Utils; using SemanticVersioning; using UnityInjector.ConsoleUtil; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: InternalsVisibleTo("BepInEx.Preloader.Core")] [assembly: InternalsVisibleTo("BepInEx.Unity.Mono")] [assembly: InternalsVisibleTo("BepInEx.NET.Framework.Launcher")] [assembly: InternalsVisibleTo("BepInEx.NET.CoreCLR")] [assembly: InternalsVisibleTo("BepInEx.Unity.IL2CPP")] [assembly: InternalsVisibleTo("BepInExTests")] [assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = "")] [assembly: AssemblyCompany("BepInEx")] [assembly: AssemblyConfiguration("Release")] [assembly: AssemblyCopyright("Copyright © 2022 BepInEx Team")] [assembly: AssemblyDescription("BepInEx Core library")] [assembly: AssemblyFileVersion("6.0.0.0")] [assembly: AssemblyInformationalVersion("6.0.0-be.674+82077ec7c91c97f0e5f8ada5d178fd7ece6c0099")] [assembly: AssemblyProduct("BepInEx.Core")] [assembly: AssemblyTitle("BepInEx.Core")] [assembly: AssemblyMetadata("RepositoryUrl", "https://github.com/BepInEx/BepInEx")] [assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)] [assembly: AssemblyVersion("6.0.0.0")] [module: UnverifiableCode] namespace System.Runtime.CompilerServices { [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct, Inherited = false)] internal sealed class InterpolatedStringHandlerAttribute : Attribute { } [AttributeUsage(AttributeTargets.Parameter)] internal sealed class InterpolatedStringHandlerArgumentAttribute : Attribute { public string[] Arguments { get; } public InterpolatedStringHandlerArgumentAttribute(string argument) { Arguments = new string[1] { argument }; } public InterpolatedStringHandlerArgumentAttribute(params string[] arguments) { Arguments = arguments; } } } namespace UnityInjector.ConsoleUtil { internal static class SafeConsole { private delegate ConsoleColor GetColorDelegate(); private delegate void SetColorDelegate(ConsoleColor value); private delegate string GetStringDelegate(); private delegate void SetStringDelegate(string value); private static GetColorDelegate _getBackgroundColor; private static SetColorDelegate _setBackgroundColor; private static GetColorDelegate _getForegroundColor; private static SetColorDelegate _setForegroundColor; private static GetStringDelegate _getTitle; private static SetStringDelegate _setTitle; public static bool BackgroundColorExists { get; private set; } public static ConsoleColor BackgroundColor { get { return _getBackgroundColor(); } set { _setBackgroundColor(value); } } public static bool ForegroundColorExists { get; private set; } public static ConsoleColor ForegroundColor { get { return _getForegroundColor(); } set { _setForegroundColor(value); } } public static bool TitleExists { get; private set; } public static string Title { get { return _getTitle(); } set { _setTitle(value); } } static SafeConsole() { InitColors(typeof(Console)); } private static void InitColors(Type tConsole) { MethodInfo method = tConsole.GetMethod("get_ForegroundColor", BindingFlags.Static | BindingFlags.Public); MethodInfo method2 = tConsole.GetMethod("set_ForegroundColor", BindingFlags.Static | BindingFlags.Public); MethodInfo method3 = tConsole.GetMethod("get_BackgroundColor", BindingFlags.Static | BindingFlags.Public); MethodInfo method4 = tConsole.GetMethod("set_BackgroundColor", BindingFlags.Static | BindingFlags.Public); MethodInfo method5 = tConsole.GetMethod("get_Title", BindingFlags.Static | BindingFlags.Public); MethodInfo method6 = tConsole.GetMethod("set_Title", BindingFlags.Static | BindingFlags.Public); _setForegroundColor = ((method2 != null) ? ((SetColorDelegate)Delegate.CreateDelegate(typeof(SetColorDelegate), method2)) : ((SetColorDelegate)delegate { })); _setBackgroundColor = ((method4 != null) ? ((SetColorDelegate)Delegate.CreateDelegate(typeof(SetColorDelegate), method4)) : ((SetColorDelegate)delegate { })); _getForegroundColor = ((method != null) ? ((GetColorDelegate)Delegate.CreateDelegate(typeof(GetColorDelegate), method)) : ((GetColorDelegate)(() => ConsoleColor.Gray))); _getBackgroundColor = ((method3 != null) ? ((GetColorDelegate)Delegate.CreateDelegate(typeof(GetColorDelegate), method3)) : ((GetColorDelegate)(() => ConsoleColor.Black))); _getTitle = ((method5 != null) ? ((GetStringDelegate)Delegate.CreateDelegate(typeof(GetStringDelegate), method5)) : ((GetStringDelegate)(() => string.Empty))); _setTitle = ((method6 != null) ? ((SetStringDelegate)Delegate.CreateDelegate(typeof(SetStringDelegate), method6)) : ((SetStringDelegate)delegate { })); BackgroundColorExists = _setBackgroundColor != null && _getBackgroundColor != null; ForegroundColorExists = _setForegroundColor != null && _getForegroundColor != null; TitleExists = _setTitle != null && _getTitle != null; } } internal class ConsoleEncoding : Encoding { private readonly byte[] _zeroByte = new byte[0]; private readonly char[] _zeroChar = new char[0]; private byte[] _byteBuffer = new byte[256]; private char[] _charBuffer = new char[256]; private readonly uint _codePage; public override int CodePage => (int)_codePage; public static Encoding OutputEncoding => new ConsoleEncoding(ConsoleCodePage); public static uint ConsoleCodePage { get { return GetConsoleOutputCP(); } set { SetConsoleOutputCP(value); } } private void ExpandByteBuffer(int count) { if (_byteBuffer.Length < count) { _byteBuffer = new byte[count]; } } private void ExpandCharBuffer(int count) { if (_charBuffer.Length < count) { _charBuffer = new char[count]; } } private void ReadByteBuffer(byte[] bytes, int index, int count) { for (int i = 0; i < count; i++) { bytes[index + i] = _byteBuffer[i]; } } private void ReadCharBuffer(char[] chars, int index, int count) { for (int i = 0; i < count; i++) { chars[index + i] = _charBuffer[i]; } } private void WriteByteBuffer(byte[] bytes, int index, int count) { ExpandByteBuffer(count); for (int i = 0; i < count; i++) { _byteBuffer[i] = bytes[index + i]; } } private void WriteCharBuffer(char[] chars, int index, int count) { ExpandCharBuffer(count); for (int i = 0; i < count; i++) { _charBuffer[i] = chars[index + i]; } } private ConsoleEncoding(uint codePage) { _codePage = codePage; } public static uint GetActiveCodePage() { return GetACP(); } public static ConsoleEncoding GetEncoding(uint codePage) { return new ConsoleEncoding(codePage); } public override int GetByteCount(char[] chars, int index, int count) { WriteCharBuffer(chars, index, count); return WideCharToMultiByte(_codePage, 0u, chars, count, _zeroByte, 0, IntPtr.Zero, IntPtr.Zero); } public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) { int byteCount = GetByteCount(chars, charIndex, charCount); WriteCharBuffer(chars, charIndex, charCount); ExpandByteBuffer(byteCount); WideCharToMultiByte(_codePage, 0u, chars, charCount, _byteBuffer, byteCount, IntPtr.Zero, IntPtr.Zero); int num = Math.Min(bytes.Length, byteCount); ReadByteBuffer(bytes, byteIndex, num); return num; } public override int GetCharCount(byte[] bytes, int index, int count) { WriteByteBuffer(bytes, index, count); return MultiByteToWideChar(_codePage, 0u, bytes, count, _zeroChar, 0); } public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) { int charCount = GetCharCount(bytes, byteIndex, byteCount); WriteByteBuffer(bytes, byteIndex, byteCount); ExpandCharBuffer(charCount); MultiByteToWideChar(_codePage, 0u, bytes, byteCount, _charBuffer, charCount); int num = Math.Min(chars.Length, charCount); ReadCharBuffer(chars, charIndex, num); return num; } public override int GetMaxByteCount(int charCount) { return charCount * 4; } public override int GetMaxCharCount(int byteCount) { return byteCount; } [DllImport("kernel32.dll")] private static extern uint GetConsoleOutputCP(); [DllImport("kernel32.dll")] private static extern uint GetACP(); [DllImport("kernel32.dll", SetLastError = true)] private static extern int MultiByteToWideChar(uint codePage, uint dwFlags, [In][MarshalAs(UnmanagedType.LPArray)] byte[] lpMultiByteStr, int cbMultiByte, [Out][MarshalAs(UnmanagedType.LPWStr)] char[] lpWideCharStr, int cchWideChar); [DllImport("kernel32.dll")] private static extern IntPtr SetConsoleOutputCP(uint codepage); [DllImport("kernel32.dll", SetLastError = true)] private static extern int WideCharToMultiByte(uint codePage, uint dwFlags, [In][MarshalAs(UnmanagedType.LPWStr)] char[] lpWideCharStr, int cchWideChar, [Out][MarshalAs(UnmanagedType.LPArray)] byte[] lpMultiByteStr, int cbMultiByte, IntPtr lpDefaultChar, IntPtr lpUsedDefaultChar); } internal class ConsoleWindow { [UnmanagedFunctionPointer(CallingConvention.Winapi)] [return: MarshalAs(UnmanagedType.Bool)] private delegate bool SetForegroundWindowDelegate(IntPtr hWnd); [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate IntPtr GetForegroundWindowDelegate(); [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate IntPtr GetSystemMenuDelegate(IntPtr hwnd, bool bRevert); [UnmanagedFunctionPointer(CallingConvention.Winapi)] private delegate bool DeleteMenuDelegate(IntPtr hMenu, uint uPosition, uint uFlags); private const int STD_OUTPUT_HANDLE = -11; private const uint SC_CLOSE = 61536u; private const uint MF_BYCOMMAND = 0u; private const uint LOAD_LIBRARY_SEARCH_SYSTEM32 = 2048u; public static IntPtr ConsoleOutHandle; public static IntPtr OriginalStdoutHandle; private static bool methodsInited; private static SetForegroundWindowDelegate setForeground; private static GetForegroundWindowDelegate getForeground; private static GetSystemMenuDelegate getSystemMenu; private static DeleteMenuDelegate deleteMenu; public static bool IsAttached { get; private set; } public static string Title { set { if (IsAttached) { if (value == null) { throw new ArgumentNullException("value"); } if (value.Length > 24500) { throw new InvalidOperationException("Console title too long"); } if (!SetConsoleTitle(value)) { throw new InvalidOperationException("Console title invalid"); } } } } public static void Attach() { if (IsAttached) { return; } Initialize(); if (OriginalStdoutHandle == IntPtr.Zero) { OriginalStdoutHandle = GetStdHandle(-11); } if (GetConsoleWindow() == IntPtr.Zero) { IntPtr hWnd = getForeground(); if (!AllocConsole() && Marshal.GetLastWin32Error() != 5) { throw new Win32Exception("AllocConsole() failed"); } setForeground(hWnd); } ConsoleOutHandle = CreateFile("CONOUT$", 3221225472u, 2, IntPtr.Zero, 3, 0, IntPtr.Zero); Kon.conOut = ConsoleOutHandle; if (!SetStdHandle(-11, ConsoleOutHandle)) { throw new Win32Exception("SetStdHandle() failed"); } if (OriginalStdoutHandle != IntPtr.Zero && ConsoleManager.ConfigConsoleOutRedirectType.Value == ConsoleManager.ConsoleOutRedirectType.ConsoleOut) { CloseHandle(OriginalStdoutHandle); } IsAttached = true; } public static void PreventClose() { if (IsAttached) { Initialize(); IntPtr consoleWindow = GetConsoleWindow(); IntPtr intPtr = getSystemMenu(consoleWindow, bRevert: false); if (intPtr != IntPtr.Zero) { deleteMenu(intPtr, 61536u, 0u); } } } public static void Detach() { if (IsAttached) { if (!CloseHandle(ConsoleOutHandle)) { throw new Win32Exception("CloseHandle() failed"); } ConsoleOutHandle = IntPtr.Zero; if (!FreeConsole()) { throw new Win32Exception("FreeConsole() failed"); } if (!SetStdHandle(-11, OriginalStdoutHandle)) { throw new Win32Exception("SetStdHandle() failed"); } IsAttached = false; } } private static void Initialize() { if (!methodsInited) { methodsInited = true; IntPtr hModule = LoadLibraryEx("user32.dll", IntPtr.Zero, 2048u); setForeground = DynDll.AsDelegate(GetProcAddress(hModule, "SetForegroundWindow")); getForeground = DynDll.AsDelegate(GetProcAddress(hModule, "GetForegroundWindow")); getSystemMenu = DynDll.AsDelegate(GetProcAddress(hModule, "GetSystemMenu")); deleteMenu = DynDll.AsDelegate(GetProcAddress(hModule, "DeleteMenu")); } } [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetProcAddress(IntPtr hModule, string procName); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool AllocConsole(); [DllImport("kernel32.dll")] private static extern IntPtr GetConsoleWindow(); [DllImport("kernel32.dll", ExactSpelling = true, SetLastError = true)] private static extern bool CloseHandle(IntPtr handle); [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] private static extern IntPtr CreateFile(string fileName, uint desiredAccess, int shareMode, IntPtr securityAttributes, int creationDisposition, int flagsAndAttributes, IntPtr templateFile); [DllImport("kernel32.dll")] private static extern bool FreeConsole(); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetStdHandle(int nStdHandle); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetStdHandle(int nStdHandle, IntPtr hConsoleOutput); [DllImport("kernel32.dll", BestFitMapping = true, CharSet = CharSet.Auto, SetLastError = true)] private static extern bool SetConsoleTitle(string title); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr LoadLibraryEx(string lpLibFileName, IntPtr hFile, uint dwFlags); } } namespace BepInEx { public static class ConsoleManager { public enum ConsoleOutRedirectType { [Description("Auto")] Auto, [Description("Console Out")] ConsoleOut, [Description("Standard Out")] StandardOut } private const uint SHIFT_JIS_CP = 932u; private const string ENABLE_CONSOLE_ARG = "--enable-console"; public static readonly ConfigEntry ConfigConsoleEnabled; public static readonly ConfigEntry ConfigPreventClose; public static readonly ConfigEntry ConfigConsoleShiftJis; public static readonly ConfigEntry ConfigConsoleOutRedirectType; private static readonly bool? EnableConsoleArgOverride; public static bool ConsoleEnabled => EnableConsoleArgOverride ?? ConfigConsoleEnabled.Value; internal static IConsoleDriver Driver { get; set; } public static bool ConsoleActive => Driver?.ConsoleActive ?? false; public static TextWriter StandardOutStream => Driver?.StandardOut; public static TextWriter ConsoleStream => Driver?.ConsoleOut; static ConsoleManager() { ConfigConsoleEnabled = ConfigFile.CoreConfig.Bind("Logging.Console", "Enabled", defaultValue: true, "Enables showing a console for log output."); ConfigPreventClose = ConfigFile.CoreConfig.Bind("Logging.Console", "PreventClose", defaultValue: false, "If enabled, will prevent closing the console (either by deleting the close button or in other platform-specific way)."); ConfigConsoleShiftJis = ConfigFile.CoreConfig.Bind("Logging.Console", "ShiftJisEncoding", defaultValue: false, "If true, console is set to the Shift-JIS encoding, otherwise UTF-8 encoding."); ConfigConsoleOutRedirectType = ConfigFile.CoreConfig.Bind("Logging.Console", "StandardOutType", ConsoleOutRedirectType.Auto, new StringBuilder().AppendLine("Hints console manager on what handle to assign as StandardOut. Possible values:").AppendLine("Auto - lets BepInEx decide how to redirect console output").AppendLine("ConsoleOut - prefer redirecting to console output; if possible, closes original standard output") .AppendLine("StandardOut - prefer redirecting to standard output; if possible, closes console out") .ToString()); try { string[] commandLineArgs = Environment.GetCommandLineArgs(); for (int i = 0; i < commandLineArgs.Length; i++) { if (commandLineArgs[i] == "--enable-console" && i + 1 < commandLineArgs.Length && bool.TryParse(commandLineArgs[i + 1], out var result)) { EnableConsoleArgOverride = result; } } } catch (Exception) { } } public static void Initialize(bool alreadyActive, bool useManagedEncoder) { //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Unknown result type (might be due to invalid IL or missing references) if (PlatformHelper.Is((Platform)8)) { Driver = new LinuxConsoleDriver(); } else { if (!PlatformHelper.Is((Platform)37)) { Platform current = PlatformHelper.Current; throw new PlatformNotSupportedException("Was unable to determine console driver for platform " + ((object)(Platform)(ref current)).ToString()); } Driver = new WindowsConsoleDriver(); } Driver.Initialize(alreadyActive, useManagedEncoder); } private static void DriverCheck() { if (Driver == null) { throw new InvalidOperationException("Driver has not been initialized"); } } public static void CreateConsole() { if (!ConsoleActive) { DriverCheck(); uint codepage = (ConfigConsoleShiftJis.Value ? 932u : ((uint)Encoding.UTF8.CodePage)); Driver.CreateConsole(codepage); if (ConfigPreventClose.Value) { Driver.PreventClose(); } } } public static void DetachConsole() { if (ConsoleActive) { DriverCheck(); Driver.DetachConsole(); } } public static void SetConsoleTitle(string title) { DriverCheck(); Driver.SetConsoleTitle(title); } public static void SetConsoleColor(ConsoleColor color) { DriverCheck(); Driver.SetConsoleColor(color); } } internal interface IConsoleDriver { TextWriter StandardOut { get; } TextWriter ConsoleOut { get; } bool ConsoleActive { get; } bool ConsoleIsExternal { get; } void PreventClose(); void Initialize(bool alreadyActive, bool useManagedEncoder); void CreateConsole(uint codepage); void DetachConsole(); void SetConsoleColor(ConsoleColor color); void SetConsoleTitle(string title); } internal class WindowsConsoleDriver : IConsoleDriver { private static readonly ConstructorInfo FileStreamCtor = new ConstructorInfo[2] { AccessTools.Constructor(typeof(FileStream), new Type[2] { typeof(SafeFileHandle), typeof(FileAccess) }, false), AccessTools.Constructor(typeof(FileStream), new Type[2] { typeof(IntPtr), typeof(FileAccess) }, false) }.FirstOrDefault((ConstructorInfo m) => m != null); private readonly Func getWindowHeight; private readonly Func getWindowWidth; private bool useManagedEncoder; private int ConsoleWidth { get { try { return getWindowWidth?.Invoke() ?? 0; } catch (IOException) { return 0; } } } private int ConsoleHeight { get { try { return getWindowHeight?.Invoke() ?? 0; } catch (IOException) { return 0; } } } public TextWriter StandardOut { get; private set; } public TextWriter ConsoleOut { get; private set; } public bool ConsoleActive { get; private set; } public bool ConsoleIsExternal => true; public void Initialize(bool alreadyActive, bool useManagedEncoder) { ConsoleActive = alreadyActive; this.useManagedEncoder = useManagedEncoder; if (ConsoleActive) { ConsoleOut = Console.Out; StandardOut = new StreamWriter(Console.OpenStandardOutput()); } else { StandardOut = Console.Out; } } public void CreateConsole(uint codepage) { ConsoleWindow.Attach(); if (!useManagedEncoder) { ConsoleEncoding.ConsoleCodePage = codepage; } IntPtr outHandle = GetOutHandle(); if (outHandle == IntPtr.Zero) { StandardOut = TextWriter.Null; ConsoleOut = TextWriter.Null; return; } Stream stream = OpenFileStream(outHandle); StandardOut = new StreamWriter(stream, Utility.UTF8NoBom) { AutoFlush = true }; Stream stream2 = OpenFileStream(ConsoleWindow.ConsoleOutHandle); ConsoleOut = new StreamWriter(stream2, useManagedEncoder ? Utility.UTF8NoBom : ConsoleEncoding.OutputEncoding) { AutoFlush = true }; ConsoleActive = true; } public void PreventClose() { ConsoleWindow.PreventClose(); } public void DetachConsole() { ConsoleWindow.Detach(); ConsoleOut.Close(); ConsoleOut = null; ConsoleActive = false; } public void SetConsoleColor(ConsoleColor color) { SafeConsole.ForegroundColor = color; Kon.ForegroundColor = color; } public void SetConsoleTitle(string title) { ConsoleWindow.Title = title; } private static Stream OpenFileStream(IntPtr handle) { if (ReflectionHelper.IsCore) { return (Stream)AccessTools.Constructor(Type.GetType("System.ConsolePal+WindowsConsoleStream, System.Console", throwOnError: true), new Type[3] { typeof(IntPtr), typeof(FileAccess), typeof(bool) }, false).Invoke(new object[3] { handle, FileAccess.Write, true }); } SafeFileHandle safeFileHandle = new SafeFileHandle(handle, ownsHandle: false); object[] args = AccessTools.ActualParameters((MethodBase)FileStreamCtor, new object[3] { safeFileHandle, safeFileHandle.DangerousGetHandle(), FileAccess.Write }); return (FileStream)Activator.CreateInstance(typeof(FileStream), args); } private IntPtr GetOutHandle() { switch (ConsoleManager.ConfigConsoleOutRedirectType.Value) { case ConsoleManager.ConsoleOutRedirectType.ConsoleOut: return ConsoleWindow.ConsoleOutHandle; case ConsoleManager.ConsoleOutRedirectType.StandardOut: return ConsoleWindow.OriginalStdoutHandle; default: if (!(ConsoleWindow.OriginalStdoutHandle != IntPtr.Zero)) { return ConsoleWindow.ConsoleOutHandle; } return ConsoleWindow.OriginalStdoutHandle; } } public WindowsConsoleDriver() { MethodInfo methodInfo = AccessTools.PropertyGetter(typeof(Console), "WindowHeight"); getWindowHeight = (((object)methodInfo != null) ? Extensions.CreateDelegate>((MethodBase)methodInfo) : null); MethodInfo methodInfo2 = AccessTools.PropertyGetter(typeof(Console), "WindowWidth"); getWindowWidth = (((object)methodInfo2 != null) ? Extensions.CreateDelegate>((MethodBase)methodInfo2) : null); base..ctor(); } } [AttributeUsage(AttributeTargets.Class)] public class BepInPlugin : Attribute { public string GUID { get; protected set; } public string Name { get; protected set; } public Version Version { get; protected set; } public BepInPlugin(string GUID, string Name, string Version) { this.GUID = GUID; this.Name = Name; this.Version = TryParseLongVersion(Version); } private static Version TryParseLongVersion(string version) { //IL_0034: Unknown result type (might be due to invalid IL or missing references) //IL_003a: Expected O, but got Unknown Version result = default(Version); if (Version.TryParse(version, ref result)) { return result; } try { Version version2 = new Version(version); return new Version(version2.Major, version2.Minor, (version2.Build != -1) ? version2.Build : 0, (string)null, (string)null); } catch { } return null; } internal static BepInPlugin FromCecilType(TypeDefinition td) { //IL_0019: Unknown result type (might be due to invalid IL or missing references) //IL_001e: Unknown result type (might be due to invalid IL or missing references) //IL_0032: Unknown result type (might be due to invalid IL or missing references) //IL_0037: Unknown result type (might be due to invalid IL or missing references) //IL_004b: Unknown result type (might be due to invalid IL or missing references) //IL_0050: Unknown result type (might be due to invalid IL or missing references) CustomAttribute val = MetadataHelper.GetCustomAttributes(td, inherit: false).FirstOrDefault(); if (val == null) { return null; } CustomAttributeArgument val2 = val.ConstructorArguments[0]; string gUID = (string)((CustomAttributeArgument)(ref val2)).Value; val2 = val.ConstructorArguments[1]; string name = (string)((CustomAttributeArgument)(ref val2)).Value; val2 = val.ConstructorArguments[2]; return new BepInPlugin(gUID, name, (string)((CustomAttributeArgument)(ref val2)).Value); } } [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class BepInDependency : Attribute, ICacheable { [Flags] public enum DependencyFlags { HardDependency = 1, SoftDependency = 2 } public string DependencyGUID { get; protected set; } public DependencyFlags Flags { get; protected set; } public Range VersionRange { get; protected set; } public BepInDependency(string DependencyGUID, DependencyFlags Flags = DependencyFlags.HardDependency) { this.DependencyGUID = DependencyGUID; this.Flags = Flags; VersionRange = null; } public BepInDependency(string guid, string version) : this(guid) { VersionRange = Range.Parse(version, false); } void ICacheable.Save(BinaryWriter bw) { bw.Write(DependencyGUID); bw.Write((int)Flags); bw.Write(((object)VersionRange)?.ToString() ?? string.Empty); } void ICacheable.Load(BinaryReader br) { DependencyGUID = br.ReadString(); Flags = (DependencyFlags)br.ReadInt32(); string text = br.ReadString(); VersionRange = ((text == string.Empty) ? null : Range.Parse(text, false)); } internal static IEnumerable FromCecilType(TypeDefinition td) { return MetadataHelper.GetCustomAttributes(td, inherit: true).Select(delegate(CustomAttribute customAttribute) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) //IL_0021: Unknown result type (might be due to invalid IL or missing references) //IL_0026: Unknown result type (might be due to invalid IL or missing references) CustomAttributeArgument val = customAttribute.ConstructorArguments[0]; string text = (string)((CustomAttributeArgument)(ref val)).Value; val = customAttribute.ConstructorArguments[1]; object value = ((CustomAttributeArgument)(ref val)).Value; return (value is string version) ? new BepInDependency(text, version) : new BepInDependency(text, (DependencyFlags)value); }).ToList(); } } [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class BepInIncompatibility : Attribute, ICacheable { public string IncompatibilityGUID { get; protected set; } public BepInIncompatibility(string IncompatibilityGUID) { this.IncompatibilityGUID = IncompatibilityGUID; } void ICacheable.Save(BinaryWriter bw) { bw.Write(IncompatibilityGUID); } void ICacheable.Load(BinaryReader br) { IncompatibilityGUID = br.ReadString(); } internal static IEnumerable FromCecilType(TypeDefinition td) { return MetadataHelper.GetCustomAttributes(td, inherit: true).Select(delegate(CustomAttribute customAttribute) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) CustomAttributeArgument val = customAttribute.ConstructorArguments[0]; return new BepInIncompatibility((string)((CustomAttributeArgument)(ref val)).Value); }).ToList(); } } [AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] public class BepInProcess : Attribute { public string ProcessName { get; protected set; } public BepInProcess(string ProcessName) { this.ProcessName = ProcessName; } internal static List FromCecilType(TypeDefinition td) { return MetadataHelper.GetCustomAttributes(td, inherit: true).Select(delegate(CustomAttribute customAttribute) { //IL_0007: Unknown result type (might be due to invalid IL or missing references) //IL_000c: Unknown result type (might be due to invalid IL or missing references) CustomAttributeArgument val = customAttribute.ConstructorArguments[0]; return new BepInProcess((string)((CustomAttributeArgument)(ref val)).Value); }).ToList(); } } public static class MetadataHelper { internal static IEnumerable GetCustomAttributes(TypeDefinition td, bool inherit) where T : Attribute { List list = new List(); Type type = typeof(T); TypeDefinition val = td; do { list.AddRange(((IEnumerable)val.CustomAttributes).Where((CustomAttribute ca) => ((MemberReference)ca.AttributeType).FullName == type.FullName)); TypeReference baseType = val.BaseType; val = ((baseType != null) ? baseType.Resolve() : null); } while (inherit && ((val != null) ? ((MemberReference)val).FullName : null) != "System.Object"); return list; } public static BepInPlugin GetMetadata(Type pluginType) { object[] customAttributes = pluginType.GetCustomAttributes(typeof(BepInPlugin), inherit: false); if (customAttributes.Length == 0) { return null; } return (BepInPlugin)customAttributes[0]; } public static BepInPlugin GetMetadata(object plugin) { return GetMetadata(plugin.GetType()); } public static T[] GetAttributes(Type pluginType) where T : Attribute { return (T[])pluginType.GetCustomAttributes(typeof(T), inherit: true); } public static T[] GetAttributes(Assembly assembly) where T : Attribute { return (T[])assembly.GetCustomAttributes(typeof(T), inherit: true); } public static IEnumerable GetAttributes(object plugin) where T : Attribute { return GetAttributes(plugin.GetType()); } public static T[] GetAttributes(MemberInfo member) where T : Attribute { return (T[])member.GetCustomAttributes(typeof(T), inherit: true); } public static IEnumerable GetDependencies(Type plugin) { return plugin.GetCustomAttributes(typeof(BepInDependency), inherit: true).Cast(); } } public class PluginInfo : ICacheable { public BepInPlugin Metadata { get; internal set; } public IEnumerable Processes { get; internal set; } public IEnumerable Dependencies { get; internal set; } public IEnumerable Incompatibilities { get; internal set; } public string Location { get; internal set; } public object Instance { get; internal set; } public string TypeName { get; internal set; } internal Version TargettedBepInExVersion { get; set; } void ICacheable.Save(BinaryWriter bw) { bw.Write(TypeName); bw.Write(Location); bw.Write(Metadata.GUID); bw.Write(Metadata.Name); bw.Write(((object)Metadata.Version).ToString()); List list = Processes.ToList(); bw.Write(list.Count); foreach (BepInProcess item in list) { bw.Write(item.ProcessName); } List list2 = Dependencies.ToList(); bw.Write(list2.Count); foreach (BepInDependency item2 in list2) { ((ICacheable)item2).Save(bw); } List list3 = Incompatibilities.ToList(); bw.Write(list3.Count); foreach (BepInIncompatibility item3 in list3) { ((ICacheable)item3).Save(bw); } bw.Write(TargettedBepInExVersion.ToString(4)); } void ICacheable.Load(BinaryReader br) { TypeName = br.ReadString(); Location = br.ReadString(); Metadata = new BepInPlugin(br.ReadString(), br.ReadString(), br.ReadString()); int num = br.ReadInt32(); List list = new List(num); for (int i = 0; i < num; i++) { list.Add(new BepInProcess(br.ReadString())); } Processes = list; int num2 = br.ReadInt32(); List list2 = new List(num2); for (int j = 0; j < num2; j++) { BepInDependency bepInDependency = new BepInDependency(""); ((ICacheable)bepInDependency).Load(br); list2.Add(bepInDependency); } Dependencies = list2; int num3 = br.ReadInt32(); List list3 = new List(num3); for (int k = 0; k < num3; k++) { BepInIncompatibility bepInIncompatibility = new BepInIncompatibility(""); ((ICacheable)bepInIncompatibility).Load(br); list3.Add(bepInIncompatibility); } Incompatibilities = list3; TargettedBepInExVersion = new Version(br.ReadString()); } public override string ToString() { return $"{Metadata?.Name} {Metadata?.Version}"; } } public static class Paths { public static Version BepInExVersion { get; } = Version.Parse(MetadataHelper.GetAttributes(typeof(Paths).Assembly)[0].InformationalVersion, false); public static string ManagedPath { get; private set; } public static string GameDataPath { get; private set; } public static string BepInExAssemblyDirectory { get; private set; } public static string BepInExAssemblyPath { get; private set; } public static string BepInExRootPath { get; private set; } public static string ExecutablePath { get; private set; } public static string GameRootPath { get; private set; } public static string ConfigPath { get; private set; } public static string BepInExConfigPath { get; private set; } public static string CachePath { get; private set; } public static string PatcherPluginPath { get; private set; } public static string PluginPath { get; private set; } public static string ProcessName { get; private set; } public static string[] DllSearchPaths { get; private set; } public static void SetExecutablePath(string executablePath, string bepinRootPath = null, string managedPath = null, bool gameDataRelativeToManaged = false, string[] dllSearchPath = null) { ExecutablePath = executablePath; ProcessName = Path.GetFileNameWithoutExtension(executablePath); GameRootPath = (PlatformHelper.Is((Platform)73) ? Utility.ParentDirectory(executablePath, 4) : Path.GetDirectoryName(executablePath)); GameDataPath = ((managedPath != null && gameDataRelativeToManaged) ? Path.GetDirectoryName(managedPath) : Path.Combine(GameRootPath, ProcessName + "_Data")); ManagedPath = managedPath ?? Path.Combine(GameDataPath, "Managed"); BepInExRootPath = bepinRootPath ?? Path.Combine(GameRootPath, "BepInEx"); ConfigPath = Path.Combine(BepInExRootPath, "config"); BepInExConfigPath = Path.Combine(ConfigPath, "BepInEx.cfg"); PluginPath = Path.Combine(BepInExRootPath, "plugins"); PatcherPluginPath = Path.Combine(BepInExRootPath, "patchers"); BepInExAssemblyDirectory = Path.Combine(BepInExRootPath, "core"); BepInExAssemblyPath = Path.Combine(BepInExAssemblyDirectory, Assembly.GetExecutingAssembly().GetName().Name + ".dll"); CachePath = Path.Combine(BepInExRootPath, "cache"); DllSearchPaths = (dllSearchPath ?? new string[0]).Concat(new string[1] { ManagedPath }).Distinct().ToArray(); } internal static void SetPluginPath(string pluginPath) { PluginPath = Utility.CombinePaths(BepInExRootPath, pluginPath); } } public static class Utility { private const string TRUSTED_PLATFORM_ASSEMBLIES = "TRUSTED_PLATFORM_ASSEMBLIES"; private static bool? sreEnabled; public static bool CLRSupportsDynamicAssemblies => CheckSRE(); public static Encoding UTF8NoBom { get; } = new UTF8Encoding(encoderShouldEmitUTF8Identifier: false); private static bool CheckSRE() { try { if (sreEnabled.HasValue) { return sreEnabled.Value; } new CustomAttributeBuilder(null, new object[0]); } catch (PlatformNotSupportedException) { sreEnabled = false; return sreEnabled.Value; } catch (ArgumentNullException) { } sreEnabled = true; return sreEnabled.Value; } public static bool TryDo(Action action, out Exception exception) { exception = null; try { action(); return true; } catch (Exception ex) { exception = ex; return false; } } public static string CombinePaths(params string[] parts) { return parts.Aggregate(Path.Combine); } public static string ParentDirectory(string path, int levels = 1) { for (int i = 0; i < levels; i++) { path = Path.GetDirectoryName(path); } return path; } public static bool SafeParseBool(string input, bool defaultValue = false) { if (!bool.TryParse(input, out var result)) { return defaultValue; } return result; } public static string ConvertToWWWFormat(string path) { return "file://" + path.Replace('\\', '/'); } public static bool IsNullOrWhiteSpace(this string self) { return self?.All(char.IsWhiteSpace) ?? true; } public static IEnumerable TopologicalSort(IEnumerable nodes, Func> dependencySelector) { List sorted_list = new List(); HashSet visited = new HashSet(); HashSet sorted = new HashSet(); foreach (TNode node in nodes) { Stack stack2 = new Stack(); if (!Visit(node, stack2)) { throw new Exception("Cyclic Dependency:\r\n" + stack2.Select((TNode x) => $" - {x}").Aggregate((string a, string b) => a + "\r\n" + b)); } } return sorted_list; bool Visit(TNode node, Stack stack) { if (visited.Contains(node)) { if (!sorted.Contains(node)) { return false; } } else { visited.Add(node); stack.Push(node); if (dependencySelector(node).Any((TNode dep) => !Visit(dep, stack))) { return false; } sorted.Add(node); sorted_list.Add(node); stack.Pop(); } return true; } } public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, Func loader, out T assembly) where T : class { assembly = null; List list = new List { directory }; if (!Directory.Exists(directory)) { return false; } list.AddRange(Directory.GetDirectories(directory, "*", SearchOption.AllDirectories)); foreach (string item in list) { string[] array = new string[2] { assemblyName.Name + ".dll", assemblyName.Name + ".exe" }; foreach (string path in array) { string text = Path.Combine(item, path); if (File.Exists(text)) { try { assembly = loader(text); } catch (Exception) { continue; } return true; } } } return false; } public static bool IsSubtypeOf(this TypeDefinition self, Type td) { if (((MemberReference)self).FullName == td.FullName) { return true; } if (((MemberReference)self).FullName != "System.Object") { TypeReference baseType = self.BaseType; return ((baseType == null) ? null : baseType.Resolve()?.IsSubtypeOf(td)).GetValueOrDefault(); } return false; } public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, out Assembly assembly) { return TryResolveDllAssembly(assemblyName, directory, (Func)Assembly.LoadFrom, out assembly); } public static bool TryResolveDllAssembly(AssemblyName assemblyName, string directory, ReaderParameters readerParameters, out AssemblyDefinition assembly) { return TryResolveDllAssembly(assemblyName, directory, (Func)((string s) => AssemblyDefinition.ReadAssembly(s, readerParameters)), out assembly); } public static bool TryOpenFileStream(string path, FileMode mode, out FileStream fileStream, FileAccess access = FileAccess.ReadWrite, FileShare share = FileShare.Read) { try { fileStream = new FileStream(path, mode, access, share); return true; } catch (IOException) { fileStream = null; return false; } } public static IEnumerable EnumerateAllMethods(this TypeDefinition type) { TypeDefinition currentType = type; while (currentType != null) { Enumerator enumerator = currentType.Methods.GetEnumerator(); try { while (enumerator.MoveNext()) { yield return enumerator.Current; } } finally { ((IDisposable)enumerator).Dispose(); } TypeReference baseType = currentType.BaseType; currentType = ((baseType != null) ? baseType.Resolve() : null); } } public static string HashStream(Stream stream) { using MD5 mD = MD5.Create(); byte[] array = new byte[4096]; int inputCount; while ((inputCount = stream.Read(array, 0, array.Length)) > 0) { mD.TransformBlock(array, 0, inputCount, array, 0); } mD.TransformFinalBlock(new byte[0], 0, 0); return ByteArrayToString(mD.Hash); } public static string HashStrings(params string[] strings) { using MD5 mD = MD5.Create(); foreach (string text in strings) { mD.TransformBlock(Encoding.UTF8.GetBytes(text), 0, text.Length, null, 0); } mD.TransformFinalBlock(new byte[0], 0, 0); return ByteArrayToString(mD.Hash); } public static string ByteArrayToString(byte[] data) { StringBuilder stringBuilder = new StringBuilder(data.Length * 2); foreach (byte b in data) { stringBuilder.AppendFormat("{0:x2}", b); } return stringBuilder.ToString(); } public static string GetCommandLineArgValue(string arg) { string[] commandLineArgs = Environment.GetCommandLineArgs(); for (int i = 1; i < commandLineArgs.Length; i++) { if (commandLineArgs[i] == arg && i + 1 < commandLineArgs.Length) { return commandLineArgs[i + 1]; } } return null; } public static bool TryParseAssemblyName(string fullName, out AssemblyName assemblyName) { try { assemblyName = new AssemblyName(fullName); return true; } catch (Exception) { assemblyName = null; return false; } } internal static void AddCecilPlatformAssemblies(this AppDomain appDomain, string assemblyDir) { if (Directory.Exists(assemblyDir)) { string text = appDomain.GetData("TRUSTED_PLATFORM_ASSEMBLIES") as string; char pathSeparator = Path.PathSeparator; string text2 = string.Join(pathSeparator.ToString(), Directory.GetFiles(assemblyDir, "*.dll", SearchOption.TopDirectoryOnly)); string data = ((text == null) ? text2 : $"{text}{Path.PathSeparator}{text2}"); appDomain.SetData("TRUSTED_PLATFORM_ASSEMBLIES", data); } } public static IEnumerable GetUniqueFilesInDirectories(IEnumerable directories, string pattern = "*") { Dictionary dictionary = new Dictionary(StringComparer.InvariantCultureIgnoreCase); foreach (string directory in directories) { string[] files = Directory.GetFiles(directory, pattern); foreach (string text in files) { string fileName = Path.GetFileName(text); if (!dictionary.ContainsKey(fileName)) { dictionary[fileName] = text; } } } return dictionary.Values; } } } namespace BepInEx.Logging { public class ConsoleLogListener : ILogListener, IDisposable { protected static readonly ConfigEntry ConfigConsoleDisplayedLevel = ConfigFile.CoreConfig.Bind("Logging.Console", "LogLevels", LogLevel.Fatal | LogLevel.Error | LogLevel.Warning | LogLevel.Message | LogLevel.Info, "Only displays the specified log levels in the console output."); public LogLevel LogLevelFilter => ConfigConsoleDisplayedLevel.Value; public void LogEvent(object sender, LogEventArgs eventArgs) { ConsoleManager.SetConsoleColor(eventArgs.Level.GetConsoleColor()); ConsoleManager.ConsoleStream?.Write(eventArgs.ToStringLine()); ConsoleManager.SetConsoleColor(ConsoleColor.Gray); } public void Dispose() { } } public class DiskLogListener : ILogListener, IDisposable { public static HashSet BlacklistedSources = new HashSet(); public LogLevel DisplayedLogLevel { get; } public TextWriter LogWriter { get; protected set; } private Timer FlushTimer { get; } private bool InstantFlushing { get; } public LogLevel LogLevelFilter => DisplayedLogLevel; public DiskLogListener(string localPath, LogLevel displayedLogLevel = LogLevel.Info, bool appendLog = false, bool delayedFlushing = true, int fileLimit = 5) { DisplayedLogLevel = displayedLogLevel; int num = 1; FileStream fileStream; while (!Utility.TryOpenFileStream(Path.Combine(Paths.BepInExRootPath, localPath), appendLog ? FileMode.Append : FileMode.Create, out fileStream, FileAccess.Write)) { if (num == fileLimit) { Logger.Log(LogLevel.Error, "Couldn't open a log file for writing. Skipping log file creation"); return; } LogLevel logLevel = LogLevel.Warning; bool isEnabled; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(56, 1, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Couldn't open log file '"); bepInExLogInterpolatedStringHandler.AppendFormatted(localPath); bepInExLogInterpolatedStringHandler.AppendLiteral("' for writing, trying another..."); } Logger.Log(logLevel, bepInExLogInterpolatedStringHandler); localPath = $"LogOutput.{num++}.log"; } LogWriter = TextWriter.Synchronized(new StreamWriter(fileStream, Utility.UTF8NoBom)); if (delayedFlushing) { FlushTimer = new Timer(delegate { LogWriter?.Flush(); }, null, 2000, 2000); } InstantFlushing = !delayedFlushing; } public void LogEvent(object sender, LogEventArgs eventArgs) { if (LogWriter != null && !BlacklistedSources.Contains(eventArgs.Source.SourceName)) { LogWriter.WriteLine(eventArgs.ToString()); if (InstantFlushing) { LogWriter.Flush(); } } } public void Dispose() { FlushTimer?.Dispose(); try { LogWriter?.Flush(); LogWriter?.Dispose(); } catch (ObjectDisposedException) { } } ~DiskLogListener() { Dispose(); } } public class HarmonyLogSource : ILogSource, IDisposable { private static readonly ConfigEntry LogChannels = ConfigFile.CoreConfig.Bind("Harmony.Logger", "LogChannels", (LogChannel)24, "Specifies which Harmony log channels to listen to.\nNOTE: IL channel dumps the whole patch methods, use only when needed!"); private static readonly Dictionary LevelMap = new Dictionary { [(LogChannel)2] = LogLevel.Info, [(LogChannel)8] = LogLevel.Warning, [(LogChannel)16] = LogLevel.Error, [(LogChannel)4] = LogLevel.Debug }; public string SourceName { get; } = "HarmonyX"; public event EventHandler LogEvent; public HarmonyLogSource() { //IL_0016: Unknown result type (might be due to invalid IL or missing references) Logger.ChannelFilter = LogChannels.Value; Logger.MessageReceived += HandleHarmonyMessage; } public void Dispose() { Logger.MessageReceived -= HandleHarmonyMessage; } private void HandleHarmonyMessage(object sender, LogEventArgs e) { //IL_0006: Unknown result type (might be due to invalid IL or missing references) if (LevelMap.TryGetValue(e.LogChannel, out var value)) { this.LogEvent?.Invoke(this, new LogEventArgs(e.Message, value, this)); } } } public interface ILogListener : IDisposable { LogLevel LogLevelFilter { get; } void LogEvent(object sender, LogEventArgs eventArgs); } public interface ILogSource : IDisposable { string SourceName { get; } event EventHandler LogEvent; } public class LogEventArgs : EventArgs { public object Data { get; } public LogLevel Level { get; } public ILogSource Source { get; } public LogEventArgs(object data, LogLevel level, ILogSource source) { Data = data; Level = level; Source = source; } public override string ToString() { return $"[{Level,-7}:{Source.SourceName,10}] {Data}"; } public string ToStringLine() { return $"[{Level,-7}:{Source.SourceName,10}] {Data}{Environment.NewLine}"; } } public static class Logger { private class LogListenerCollection : List, ICollection, IEnumerable, IEnumerable { public LogLevel activeLogLevels; void ICollection.Add(ILogListener item) { if (item == null) { throw new ArgumentNullException("item"); } activeLogLevels |= item.LogLevelFilter; Add(item); } void ICollection.Clear() { activeLogLevels = LogLevel.None; Clear(); } bool ICollection.Remove(ILogListener item) { if (item == null || !Remove(item)) { return false; } activeLogLevels = LogLevel.None; using (Enumerator enumerator = GetEnumerator()) { while (enumerator.MoveNext()) { ILogListener current = enumerator.Current; activeLogLevels |= current.LogLevelFilter; } } return true; } } private class LogSourceCollection : List, ICollection, IEnumerable, IEnumerable { void ICollection.Add(ILogSource item) { if (item == null) { throw new ArgumentNullException("item", "Log sources cannot be null when added to the source list."); } item.LogEvent += InternalLogEvent; Add(item); } void ICollection.Clear() { using (Enumerator enumerator = GetEnumerator()) { while (enumerator.MoveNext()) { enumerator.Current.LogEvent -= InternalLogEvent; } } Clear(); } bool ICollection.Remove(ILogSource item) { if (item == null || !Remove(item)) { return false; } item.LogEvent -= InternalLogEvent; return true; } } private static readonly ManualLogSource InternalLogSource; private static readonly LogListenerCollection listeners; public static LogLevel ListenedLogLevels => listeners.activeLogLevels; public static ICollection Listeners => listeners; public static ICollection Sources { get; } static Logger() { Sources = new LogSourceCollection(); listeners = new LogListenerCollection(); InternalLogSource = CreateLogSource("BepInEx"); } internal static void InternalLogEvent(object sender, LogEventArgs eventArgs) { foreach (ILogListener listener in listeners) { if ((eventArgs.Level & listener.LogLevelFilter) != 0) { listener?.LogEvent(sender, eventArgs); } } } internal static void Log(LogLevel level, object data) { InternalLogSource.Log(level, data); } internal static void Log(LogLevel level, [InterpolatedStringHandlerArgument("level")] BepInExLogInterpolatedStringHandler logHandler) { InternalLogSource.Log(level, logHandler); } public static ManualLogSource CreateLogSource(string sourceName) { ManualLogSource manualLogSource = new ManualLogSource(sourceName); Sources.Add(manualLogSource); return manualLogSource; } } [Flags] public enum LogLevel { None = 0, Fatal = 1, Error = 2, Warning = 4, Message = 8, Info = 0x10, Debug = 0x20, All = 0x3F } public static class LogLevelExtensions { public static LogLevel GetHighestLevel(this LogLevel levels) { Array values = Enum.GetValues(typeof(LogLevel)); Array.Sort(values); foreach (LogLevel item in values) { if ((levels & item) != 0) { return item; } } return LogLevel.None; } public static ConsoleColor GetConsoleColor(this LogLevel level) { level = level.GetHighestLevel(); return level switch { LogLevel.Fatal => ConsoleColor.Red, LogLevel.Error => ConsoleColor.DarkRed, LogLevel.Warning => ConsoleColor.Yellow, LogLevel.Message => ConsoleColor.White, LogLevel.Info => ConsoleColor.DarkGray, LogLevel.Debug => ConsoleColor.DarkGray, _ => ConsoleColor.Gray, }; } } public class ManualLogSource : ILogSource, IDisposable { public string SourceName { get; } public event EventHandler LogEvent; public ManualLogSource(string sourceName) { SourceName = sourceName; } public void Dispose() { } public void Log(LogLevel level, object data) { this.LogEvent?.Invoke(this, new LogEventArgs(data, level, this)); } public void Log(LogLevel level, [InterpolatedStringHandlerArgument("level")] BepInExLogInterpolatedStringHandler logHandler) { if (logHandler.Enabled) { this.LogEvent?.Invoke(this, new LogEventArgs(logHandler.ToString(), level, this)); } } public void LogFatal(object data) { Log(LogLevel.Fatal, data); } public void LogFatal(BepInExFatalLogInterpolatedStringHandler logHandler) { Log(LogLevel.Fatal, logHandler); } public void LogError(object data) { Log(LogLevel.Error, data); } public void LogError(BepInExErrorLogInterpolatedStringHandler logHandler) { Log(LogLevel.Error, logHandler); } public void LogWarning(object data) { Log(LogLevel.Warning, data); } public void LogWarning(BepInExWarningLogInterpolatedStringHandler logHandler) { Log(LogLevel.Warning, logHandler); } public void LogMessage(object data) { Log(LogLevel.Message, data); } public void LogMessage(BepInExMessageLogInterpolatedStringHandler logHandler) { Log(LogLevel.Message, logHandler); } public void LogInfo(object data) { Log(LogLevel.Info, data); } public void LogInfo(BepInExInfoLogInterpolatedStringHandler logHandler) { Log(LogLevel.Info, logHandler); } public void LogDebug(object data) { Log(LogLevel.Debug, data); } public void LogDebug(BepInExDebugLogInterpolatedStringHandler logHandler) { Log(LogLevel.Debug, logHandler); } } public class TraceLogSource : TraceListener { private static TraceLogSource traceListener; public static bool IsListening { get; private set; } protected ManualLogSource LogSource { get; } protected TraceLogSource() { LogSource = new ManualLogSource("Trace"); } public static ILogSource CreateSource() { if (traceListener == null) { traceListener = new TraceLogSource(); Trace.Listeners.Add(traceListener); IsListening = true; } return traceListener.LogSource; } public override void Write(string message) { LogSource.Log(LogLevel.Info, message); } public override void WriteLine(string message) { LogSource.Log(LogLevel.Info, message); } public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string format, params object[] args) { TraceEvent(eventCache, source, eventType, id, string.Format(format, args)); } public override void TraceEvent(TraceEventCache eventCache, string source, TraceEventType eventType, int id, string message) { LogSource.Log(eventType switch { TraceEventType.Critical => LogLevel.Fatal, TraceEventType.Error => LogLevel.Error, TraceEventType.Warning => LogLevel.Warning, TraceEventType.Information => LogLevel.Info, _ => LogLevel.Debug, }, (message ?? "").Trim()); } } } namespace BepInEx.Core.Logging.Interpolation { [InterpolatedStringHandler] public class BepInExLogInterpolatedStringHandler { private const int GUESSED_LENGTH_PER_HOLE = 11; private readonly StringBuilder sb; public bool Enabled { get; } public BepInExLogInterpolatedStringHandler(int literalLength, int formattedCount, LogLevel logLevel, out bool isEnabled) { Enabled = (logLevel & Logger.ListenedLogLevels) != 0; isEnabled = Enabled; sb = (Enabled ? new StringBuilder(literalLength + formattedCount * 11) : null); } public void AppendLiteral(string s) { if (Enabled) { sb.Append(s); } } public void AppendFormatted(T t) { if (Enabled) { sb.Append(t); } } public void AppendFormatted(T t, string format) where T : IFormattable { if (Enabled) { sb.Append(t?.ToString(format, null)); } } public void AppendFormatted(IntPtr t, string format) { if (Enabled) { sb.Append(t.ToString(format)); } } public override string ToString() { return sb?.ToString() ?? string.Empty; } } [InterpolatedStringHandler] public class BepInExFatalLogInterpolatedStringHandler : BepInExLogInterpolatedStringHandler { public BepInExFatalLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled) : base(literalLength, formattedCount, LogLevel.Fatal, out isEnabled) { } } [InterpolatedStringHandler] public class BepInExErrorLogInterpolatedStringHandler : BepInExLogInterpolatedStringHandler { public BepInExErrorLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled) : base(literalLength, formattedCount, LogLevel.Error, out isEnabled) { } } [InterpolatedStringHandler] public class BepInExWarningLogInterpolatedStringHandler : BepInExLogInterpolatedStringHandler { public BepInExWarningLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled) : base(literalLength, formattedCount, LogLevel.Warning, out isEnabled) { } } [InterpolatedStringHandler] public class BepInExMessageLogInterpolatedStringHandler : BepInExLogInterpolatedStringHandler { public BepInExMessageLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled) : base(literalLength, formattedCount, LogLevel.Message, out isEnabled) { } } [InterpolatedStringHandler] public class BepInExInfoLogInterpolatedStringHandler : BepInExLogInterpolatedStringHandler { public BepInExInfoLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled) : base(literalLength, formattedCount, LogLevel.Info, out isEnabled) { } } [InterpolatedStringHandler] public class BepInExDebugLogInterpolatedStringHandler : BepInExLogInterpolatedStringHandler { public BepInExDebugLogInterpolatedStringHandler(int literalLength, int formattedCount, out bool isEnabled) : base(literalLength, formattedCount, LogLevel.Debug, out isEnabled) { } } } namespace BepInEx.ConsoleUtil { internal class Kon { private struct CONSOLE_SCREEN_BUFFER_INFO { internal COORD dwSize; internal COORD dwCursorPosition; internal short wAttributes; internal SMALL_RECT srWindow; internal COORD dwMaximumWindowSize; } private struct COORD { internal short X; internal short Y; } private struct SMALL_RECT { internal short Left; internal short Top; internal short Right; internal short Bottom; } private static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); internal static IntPtr conOut = IntPtr.Zero; public static ConsoleColor ForegroundColor { get { return GetConsoleColor(isBackground: false); } set { SetConsoleColor(isBackground: false, value); } } public static ConsoleColor BackgroundColor { get { return GetConsoleColor(isBackground: true); } set { SetConsoleColor(isBackground: true, value); } } [DllImport("kernel32.dll", SetLastError = true)] private static extern bool GetConsoleScreenBufferInfo(IntPtr hConsoleOutput, out CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo); [DllImport("kernel32.dll", SetLastError = true)] private static extern bool SetConsoleTextAttribute(IntPtr hConsoleOutput, short attributes); [DllImport("kernel32.dll", SetLastError = true)] private static extern IntPtr GetStdHandle(int nStdHandle); private static short ConsoleColorToColorAttribute(short color, bool isBackground) { if (((uint)color & 0xFFFFFFF0u) != 0) { throw new ArgumentException("Arg_InvalidConsoleColor"); } if (isBackground) { color <<= 4; } return color; } private static CONSOLE_SCREEN_BUFFER_INFO GetBufferInfo(bool throwOnNoConsole, out bool succeeded) { succeeded = false; if (!(conOut == INVALID_HANDLE_VALUE)) { if (!GetConsoleScreenBufferInfo(conOut, out var lpConsoleScreenBufferInfo)) { bool consoleScreenBufferInfo = GetConsoleScreenBufferInfo(GetStdHandle(-12), out lpConsoleScreenBufferInfo); if (!consoleScreenBufferInfo) { consoleScreenBufferInfo = GetConsoleScreenBufferInfo(GetStdHandle(-10), out lpConsoleScreenBufferInfo); } if (!consoleScreenBufferInfo && Marshal.GetLastWin32Error() == 6 && !throwOnNoConsole) { return default(CONSOLE_SCREEN_BUFFER_INFO); } } succeeded = true; return lpConsoleScreenBufferInfo; } if (!throwOnNoConsole) { return default(CONSOLE_SCREEN_BUFFER_INFO); } throw new Exception("IO.IO_NoConsole"); } private static void SetConsoleColor(bool isBackground, ConsoleColor c) { short num = ConsoleColorToColorAttribute((short)c, isBackground); bool succeeded; CONSOLE_SCREEN_BUFFER_INFO bufferInfo = GetBufferInfo(throwOnNoConsole: false, out succeeded); if (succeeded) { short wAttributes = bufferInfo.wAttributes; wAttributes &= (short)(isBackground ? (-241) : (-16)); wAttributes = (short)((ushort)wAttributes | (ushort)num); SetConsoleTextAttribute(conOut, wAttributes); } } private static ConsoleColor GetConsoleColor(bool isBackground) { bool succeeded; CONSOLE_SCREEN_BUFFER_INFO bufferInfo = GetBufferInfo(throwOnNoConsole: false, out succeeded); if (!succeeded) { if (!isBackground) { return ConsoleColor.Gray; } return ConsoleColor.Black; } return ColorAttributeToConsoleColor((short)(bufferInfo.wAttributes & 0xF0)); } private static ConsoleColor ColorAttributeToConsoleColor(short c) { if ((short)((uint)c & 0xFFu) != 0) { c >>= 4; } return (ConsoleColor)c; } public static void ResetConsoleColor() { SetConsoleColor(isBackground: true, ConsoleColor.Black); SetConsoleColor(isBackground: false, ConsoleColor.Gray); } } } namespace BepInEx.Unix { internal static class ConsoleWriter { private static Func cStreamWriterConstructor; private static Func CStreamWriterConstructor { get { if (cStreamWriterConstructor != null) { return cStreamWriterConstructor; } Type cStreamWriter = AccessTools.TypeByName("System.IO.CStreamWriter"); cStreamWriterConstructor = new int[2][] { new int[3] { 0, 1, 2 }, new int[2] { 0, 1 } }.Select(GetCtor).FirstOrDefault((Func f) => f != null); if (cStreamWriterConstructor == null) { throw new AmbiguousMatchException("Failed to find suitable constructor for CStreamWriter"); } return cStreamWriterConstructor; Func GetCtor(int[] perm) { Type[] parameters = new Type[3] { typeof(Stream), typeof(Encoding), typeof(bool) }; ConstructorInfo ctor = AccessTools.Constructor(cStreamWriter, perm.Select((int i) => parameters[i]).ToArray(), false); if (ctor != null) { return delegate(Stream stream, Encoding encoding, bool l) { object[] vals = new object[3] { stream, encoding, l }; return (StreamWriter)ctor.Invoke(perm.Select((int i) => vals[i]).ToArray()); }; } return null; } } } public static TextWriter CreateConsoleStreamWriter(Stream stream, Encoding encoding, bool leaveOpen) { StreamWriter streamWriter = CStreamWriterConstructor(stream, encoding, leaveOpen); streamWriter.AutoFlush = true; return streamWriter; } } internal class LinuxConsoleDriver : IConsoleDriver { private static readonly ConfigEntry ForceCustomTtyDriverConfig; public static bool UseMonoTtyDriver { get; } public bool StdoutRedirected { get; private set; } public TtyInfo TtyInfo { get; private set; } public TextWriter StandardOut { get; private set; } public TextWriter ConsoleOut { get; private set; } public bool ConsoleActive { get; private set; } public bool ConsoleIsExternal => false; static LinuxConsoleDriver() { ForceCustomTtyDriverConfig = ConfigFile.CoreConfig.Bind("Logging.Console", "ForceBepInExTTYDriver", defaultValue: false, "If enabled, forces to use custom BepInEx TTY driver for handling terminal output on unix."); UseMonoTtyDriver = false; if (!ForceCustomTtyDriverConfig.Value && typeof(Console).Assembly.GetType("System.ConsoleDriver") != null) { UseMonoTtyDriver = typeof(Console).Assembly.GetType("System.ParameterizedStrings") != null; } } public void PreventClose() { } public void Initialize(bool alreadyActive, bool useManagedEncoder) { ConsoleActive = true; StdoutRedirected = UnixStreamHelper.isatty(1) != 1; Stream stream = UnixStreamHelper.CreateDuplicateStream(1); if (UseMonoTtyDriver && !StdoutRedirected) { TextWriter textWriter = ConsoleWriter.CreateConsoleStreamWriter(stream, Console.Out.Encoding, leaveOpen: true); StandardOut = TextWriter.Synchronized(textWriter); object value = AccessTools.Field(AccessTools.TypeByName("System.ConsoleDriver"), "driver").GetValue(null); AccessTools.Field(AccessTools.TypeByName("System.TermInfoDriver"), "stdout").SetValue(value, textWriter); } else { StreamWriter streamWriter = new StreamWriter(stream, Console.Out.Encoding); streamWriter.AutoFlush = true; StandardOut = TextWriter.Synchronized(streamWriter); TtyInfo = TtyHandler.GetTtyInfo(); } ConsoleOut = StandardOut; } public void CreateConsole(uint codepage) { Logger.Log(LogLevel.Warning, "An external console currently cannot be spawned on a Unix platform."); } public void DetachConsole() { throw new PlatformNotSupportedException("Cannot detach console on a Unix platform"); } public void SetConsoleColor(ConsoleColor color) { if (!StdoutRedirected) { if (UseMonoTtyDriver) { SafeConsole.ForegroundColor = color; } else { ConsoleOut.Write(TtyInfo.GetAnsiCode(color)); } } } public void SetConsoleTitle(string title) { if (!StdoutRedirected) { if (UseMonoTtyDriver && SafeConsole.TitleExists) { SafeConsole.Title = title; } else { ConsoleOut.Write("\u001b]2;" + title.Replace("\\", "\\\\") + "\a"); } } } } internal class TtyInfo { public string TerminalType { get; set; } = "default"; public int MaxColors { get; set; } public string[] ForegroundColorStrings { get; set; } public static TtyInfo Default { get; } = new TtyInfo { MaxColors = 0 }; public string GetAnsiCode(ConsoleColor color) { if (MaxColors <= 0 || ForegroundColorStrings == null) { return string.Empty; } int num = (int)color % MaxColors; return ForegroundColorStrings[num]; } } internal static class TtyHandler { private static readonly string[] ncursesLocations = new string[4] { "/usr/share/terminfo", "/etc/terminfo", "/usr/lib/terminfo", "/lib/terminfo" }; private static string TryTermInfoDir(string dir, string term) { string text = $"{dir}/{(int)term[0]:x}/{term}"; if (File.Exists(text)) { return text; } text = Utility.CombinePaths(dir, term.Substring(0, 1), term); if (File.Exists(text)) { return text; } return null; } private static string FindTermInfoPath(string term) { if (string.IsNullOrEmpty(term)) { return null; } string environmentVariable = Environment.GetEnvironmentVariable("TERMINFO"); if (environmentVariable != null && Directory.Exists(environmentVariable)) { string text = TryTermInfoDir(environmentVariable, term); if (text != null) { return text; } } string[] array = ncursesLocations; foreach (string text2 in array) { if (Directory.Exists(text2)) { string text3 = TryTermInfoDir(text2, term); if (text3 != null) { return text3; } } } return null; } public static TtyInfo GetTtyInfo(string terminal = null) { terminal = terminal ?? Environment.GetEnvironmentVariable("TERM"); string text = FindTermInfoPath(terminal); if (text == null) { return TtyInfo.Default; } TtyInfo ttyInfo = TtyInfoParser.Parse(File.ReadAllBytes(text)); ttyInfo.TerminalType = terminal; return ttyInfo; } } internal static class TtyInfoParser { internal enum TermInfoNumbers { MaxColors = 13 } internal enum TermInfoStrings { SetAForeground = 359 } private static readonly int[] ansiColorMapping = new int[16] { 0, 4, 2, 6, 1, 5, 3, 7, 8, 12, 10, 14, 9, 13, 11, 15 }; public static TtyInfo Parse(byte[] buffer) { int num; switch (GetInt16(buffer, 0)) { case 282: num = 2; break; case 542: num = 4; break; default: return TtyInfo.Default; } int @int = GetInt16(buffer, 4); GetInt16(buffer, 6); GetInt16(buffer, 8); int num2 = 12 + GetString(buffer, 12).Length + 1 + @int; int offset = num2 + num2 % 2 + num * 13; return new TtyInfo { MaxColors = GetInteger(num, buffer, offset), ForegroundColorStrings = ansiColorMapping.Select((int x) => $"\u001b[{((x > 7) ? (82 + x) : (30 + x))}m").ToArray() }; } private static int GetInt32(byte[] buffer, int offset) { return buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16) | (buffer[offset + 3] << 24); } private static short GetInt16(byte[] buffer, int offset) { return (short)(buffer[offset] | (buffer[offset + 1] << 8)); } private static int GetInteger(int intSize, byte[] buffer, int offset) { if (intSize != 2) { return GetInt32(buffer, offset); } return GetInt16(buffer, offset); } private static string GetString(byte[] buffer, int offset) { int i; for (i = 0; buffer[offset + i] != 0; i++) { } return Encoding.ASCII.GetString(buffer, offset, i); } } internal class UnixStream : Stream { public override bool CanRead { get { if (Access != FileAccess.Read) { return Access == FileAccess.ReadWrite; } return true; } } public override bool CanSeek => false; public override bool CanWrite { get { if (Access != FileAccess.Write) { return Access == FileAccess.ReadWrite; } return true; } } public override long Length { get { throw new InvalidOperationException(); } } public override long Position { get { throw new InvalidOperationException(); } set { throw new InvalidOperationException(); } } public FileAccess Access { get; } public IntPtr FileHandle { get; } public UnixStream(int fileDescriptor, FileAccess access) { Access = access; int fd = UnixStreamHelper.dup(fileDescriptor); FileHandle = UnixStreamHelper.fdopen(fd, (access == FileAccess.Write) ? "w" : "r"); } public override void Flush() { UnixStreamHelper.fflush(FileHandle); } public override long Seek(long offset, SeekOrigin origin) { throw new InvalidOperationException(); } public override void SetLength(long value) { throw new InvalidOperationException(); } public override int Read(byte[] buffer, int offset, int count) { GCHandle gCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); IntPtr intPtr = UnixStreamHelper.fread(new IntPtr(gCHandle.AddrOfPinnedObject().ToInt64() + offset), (IntPtr)count, (IntPtr)1, FileHandle); gCHandle.Free(); return intPtr.ToInt32(); } public override void Write(byte[] buffer, int offset, int count) { GCHandle gCHandle = GCHandle.Alloc(buffer, GCHandleType.Pinned); UnixStreamHelper.fwrite(new IntPtr(gCHandle.AddrOfPinnedObject().ToInt64() + offset), (IntPtr)count, (IntPtr)1, FileHandle); gCHandle.Free(); } private void ReleaseUnmanagedResources() { UnixStreamHelper.fclose(FileHandle); } protected override void Dispose(bool disposing) { ReleaseUnmanagedResources(); base.Dispose(disposing); } ~UnixStream() { Dispose(disposing: false); } } internal static class UnixStreamHelper { public delegate int dupDelegate(int fd); public delegate int fcloseDelegate(IntPtr stream); public delegate IntPtr fdopenDelegate(int fd, string mode); public delegate int fflushDelegate(IntPtr stream); public delegate IntPtr freadDelegate(IntPtr ptr, IntPtr size, IntPtr nmemb, IntPtr stream); public delegate int fwriteDelegate(IntPtr ptr, IntPtr size, IntPtr nmemb, IntPtr stream); public delegate int isattyDelegate(int fd); [DynDllImport("libc", new string[] { })] public static dupDelegate dup; [DynDllImport("libc", new string[] { })] public static fdopenDelegate fdopen; [DynDllImport("libc", new string[] { })] public static freadDelegate fread; [DynDllImport("libc", new string[] { })] public static fwriteDelegate fwrite; [DynDllImport("libc", new string[] { })] public static fcloseDelegate fclose; [DynDllImport("libc", new string[] { })] public static fflushDelegate fflush; [DynDllImport("libc", new string[] { })] public static isattyDelegate isatty; static UnixStreamHelper() { Dictionary> dictionary = new Dictionary> { ["libc"] = new List { DynDllMapping.op_Implicit("libc.so.6"), DynDllMapping.op_Implicit("libc"), DynDllMapping.op_Implicit("/usr/lib/libSystem.dylib") } }; DynDll.ResolveDynDllImports(typeof(UnixStreamHelper), dictionary); } public static Stream CreateDuplicateStream(int fileDescriptor) { return new UnixStream(dup(fileDescriptor), FileAccess.Write); } } } namespace BepInEx.Configuration { public abstract class AcceptableValueBase { public Type ValueType { get; } protected AcceptableValueBase(Type valueType) { ValueType = valueType; } public abstract object Clamp(object value); public abstract bool IsValid(object value); public abstract string ToDescriptionString(); } public class AcceptableValueList : AcceptableValueBase where T : IEquatable { public virtual T[] AcceptableValues { get; } public AcceptableValueList(params T[] acceptableValues) : base(typeof(T)) { if (acceptableValues == null) { throw new ArgumentNullException("acceptableValues"); } if (acceptableValues.Length == 0) { throw new ArgumentException("At least one acceptable value is needed", "acceptableValues"); } AcceptableValues = acceptableValues; } public override object Clamp(object value) { if (IsValid(value)) { return value; } return AcceptableValues[0]; } public override bool IsValid(object value) { if (value is T) { T v = (T)value; return AcceptableValues.Any((T x) => x.Equals(v)); } return false; } public override string ToDescriptionString() { return "# Acceptable values: " + string.Join(", ", AcceptableValues.Select((T x) => x.ToString()).ToArray()); } } public class AcceptableValueRange : AcceptableValueBase where T : IComparable { public virtual T MinValue { get; } public virtual T MaxValue { get; } public AcceptableValueRange(T minValue, T maxValue) : base(typeof(T)) { if (maxValue == null) { throw new ArgumentNullException("maxValue"); } if (minValue == null) { throw new ArgumentNullException("minValue"); } if (minValue.CompareTo(maxValue) >= 0) { throw new ArgumentException("minValue has to be lower than maxValue"); } MinValue = minValue; MaxValue = maxValue; } public override object Clamp(object value) { if (MinValue.CompareTo(value) > 0) { return MinValue; } if (MaxValue.CompareTo(value) < 0) { return MaxValue; } return value; } public override bool IsValid(object value) { if (MinValue.CompareTo(value) <= 0) { return MaxValue.CompareTo(value) >= 0; } return false; } public override string ToDescriptionString() { return $"# Acceptable value range: From {MinValue} to {MaxValue}"; } } public class ConfigDefinition : IEquatable { private static readonly char[] _invalidConfigChars = new char[8] { '=', '\n', '\t', '\\', '"', '\'', '[', ']' }; public string Section { get; } public string Key { get; } public ConfigDefinition(string section, string key) { CheckInvalidConfigChars(section, "section"); CheckInvalidConfigChars(key, "key"); Key = key; Section = section; } [Obsolete("description argument is no longer used, put it in a ConfigDescription instead")] public ConfigDefinition(string section, string key, string description) { Key = key ?? ""; Section = section ?? ""; } public bool Equals(ConfigDefinition other) { if (other == null) { return false; } if (string.Equals(Key, other.Key)) { return string.Equals(Section, other.Section); } return false; } private static void CheckInvalidConfigChars(string val, string name) { if (val == null) { throw new ArgumentNullException(name); } if (val != val.Trim()) { throw new ArgumentException("Cannot use whitespace characters at start or end of section and key names", name); } if (val.Any((char c) => _invalidConfigChars.Contains(c))) { throw new ArgumentException("Cannot use any of the following characters in section and key names: = \\n \\t \\ \" ' [ ]", name); } } public override bool Equals(object obj) { if (obj == null) { return false; } if (this == obj) { return true; } return Equals(obj as ConfigDefinition); } public override int GetHashCode() { return (((Key != null) ? Key.GetHashCode() : 0) * 397) ^ ((Section != null) ? Section.GetHashCode() : 0); } public static bool operator ==(ConfigDefinition left, ConfigDefinition right) { return object.Equals(left, right); } public static bool operator !=(ConfigDefinition left, ConfigDefinition right) { return !object.Equals(left, right); } public override string ToString() { return Section + "." + Key; } } public class ConfigDescription { public string Description { get; } public AcceptableValueBase AcceptableValues { get; } public object[] Tags { get; } public static ConfigDescription Empty { get; } = new ConfigDescription("", null); public ConfigDescription(string description, AcceptableValueBase acceptableValues = null, params object[] tags) { AcceptableValues = acceptableValues; Tags = tags; Description = description ?? throw new ArgumentNullException("description"); } } public sealed class ConfigEntry : ConfigEntryBase { private T _typedValue; public T Value { get { return _typedValue; } set { value = ClampValue(value); if (!object.Equals(_typedValue, value)) { _typedValue = value; OnSettingChanged(this); } } } public override object BoxedValue { get { return Value; } set { Value = (T)value; } } public event EventHandler SettingChanged; internal ConfigEntry(ConfigFile configFile, ConfigDefinition definition, T defaultValue, ConfigDescription configDescription) : base(configFile, definition, typeof(T), defaultValue, configDescription) { configFile.SettingChanged += delegate(object sender, SettingChangedEventArgs args) { if (args.ChangedSetting == this) { this.SettingChanged?.Invoke(sender, args); } }; } } public abstract class ConfigEntryBase { public ConfigFile ConfigFile { get; } public ConfigDefinition Definition { get; } public ConfigDescription Description { get; } public Type SettingType { get; } public object DefaultValue { get; } public abstract object BoxedValue { get; set; } protected internal ConfigEntryBase(ConfigFile configFile, ConfigDefinition definition, Type settingType, object defaultValue, ConfigDescription configDescription) { ConfigFile = configFile ?? throw new ArgumentNullException("configFile"); Definition = definition ?? throw new ArgumentNullException("definition"); SettingType = settingType ?? throw new ArgumentNullException("settingType"); Description = configDescription ?? ConfigDescription.Empty; if (Description.AcceptableValues != null && !SettingType.IsAssignableFrom(Description.AcceptableValues.ValueType)) { throw new ArgumentException("configDescription.AcceptableValues is for a different type than the type of this setting"); } DefaultValue = defaultValue; BoxedValue = defaultValue; } public string GetSerializedValue() { return TomlTypeConverter.ConvertToString(BoxedValue, SettingType); } public void SetSerializedValue(string value) { try { object boxedValue = TomlTypeConverter.ConvertToValue(value, SettingType); BoxedValue = boxedValue; } catch (Exception ex) { LogLevel logLevel = LogLevel.Warning; bool isEnabled; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(85, 3, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Config value of setting \""); bepInExLogInterpolatedStringHandler.AppendFormatted(Definition); bepInExLogInterpolatedStringHandler.AppendLiteral("\" could not be parsed and will be ignored. Reason: "); bepInExLogInterpolatedStringHandler.AppendFormatted(ex.Message); bepInExLogInterpolatedStringHandler.AppendLiteral("; Value: "); bepInExLogInterpolatedStringHandler.AppendFormatted(value); } Logger.Log(logLevel, bepInExLogInterpolatedStringHandler); } } protected T ClampValue(T value) { if (Description.AcceptableValues != null) { return (T)Description.AcceptableValues.Clamp(value); } return value; } protected void OnSettingChanged(object sender) { ConfigFile.OnSettingChanged(sender, this); } public void WriteDescription(StreamWriter writer) { if (!string.IsNullOrEmpty(Description.Description)) { writer.WriteLine("## " + Description.Description.Replace("\n", "\n## ")); } writer.WriteLine("# Setting type: " + SettingType.Name); writer.WriteLine("# Default value: " + TomlTypeConverter.ConvertToString(DefaultValue, SettingType)); if (Description.AcceptableValues != null) { writer.WriteLine(Description.AcceptableValues.ToDescriptionString()); } else if (SettingType.IsEnum) { writer.WriteLine("# Acceptable values: " + string.Join(", ", Enum.GetNames(SettingType))); if (SettingType.GetCustomAttributes(typeof(FlagsAttribute), inherit: true).Any()) { writer.WriteLine("# Multiple values can be set at the same time by separating them with , (e.g. Debug, Warning)"); } } } } public class ConfigFile : IDictionary, ICollection>, IEnumerable>, IEnumerable { private readonly BepInPlugin _ownerMetadata; private readonly object _ioLock = new object(); public static ConfigFile CoreConfig { get; } = new ConfigFile(Paths.BepInExConfigPath, saveOnInit: true); protected Dictionary Entries { get; } = new Dictionary(); private Dictionary OrphanedEntries { get; } = new Dictionary(); [Obsolete("Use Keys instead")] public ReadOnlyCollection ConfigDefinitions { get { lock (_ioLock) { return Entries.Keys.ToList().AsReadOnly(); } } } public string ConfigFilePath { get; } public bool SaveOnConfigSet { get; set; } = true; public ConfigEntryBase this[ConfigDefinition key] { get { lock (_ioLock) { return Entries[key]; } } } public ConfigEntryBase this[string section, string key] => this[new ConfigDefinition(section, key)]; public int Count { get { lock (_ioLock) { return Entries.Count; } } } public bool IsReadOnly => false; ConfigEntryBase IDictionary.this[ConfigDefinition key] { get { lock (_ioLock) { return Entries[key]; } } set { throw new InvalidOperationException("Directly setting a config entry is not supported"); } } public ICollection Keys { get { lock (_ioLock) { return Entries.Keys.ToArray(); } } } public ICollection Values { get { lock (_ioLock) { return Entries.Values.ToArray(); } } } public bool GenerateSettingDescriptions { get; set; } = true; public event EventHandler ConfigReloaded; public event EventHandler SettingChanged; public ConfigFile(string configPath, bool saveOnInit) : this(configPath, saveOnInit, null) { } public ConfigFile(string configPath, bool saveOnInit, BepInPlugin ownerMetadata) { _ownerMetadata = ownerMetadata; if (configPath == null) { throw new ArgumentNullException("configPath"); } configPath = Path.GetFullPath(configPath); ConfigFilePath = configPath; if (File.Exists(ConfigFilePath)) { Reload(); } else if (saveOnInit) { Save(); } } public IEnumerator> GetEnumerator() { return Entries.GetEnumerator(); } IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); } void ICollection>.Add(KeyValuePair item) { lock (_ioLock) { Entries.Add(item.Key, item.Value); } } public bool Contains(KeyValuePair item) { lock (_ioLock) { return ((ICollection>)Entries).Contains(item); } } void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { lock (_ioLock) { ((ICollection>)Entries).CopyTo(array, arrayIndex); } } bool ICollection>.Remove(KeyValuePair item) { lock (_ioLock) { return Entries.Remove(item.Key); } } public bool ContainsKey(ConfigDefinition key) { lock (_ioLock) { return Entries.ContainsKey(key); } } public void Add(ConfigDefinition key, ConfigEntryBase value) { throw new InvalidOperationException("Directly adding a config entry is not supported"); } public bool Remove(ConfigDefinition key) { lock (_ioLock) { return Entries.Remove(key); } } public void Clear() { lock (_ioLock) { Entries.Clear(); } } bool IDictionary.TryGetValue(ConfigDefinition key, out ConfigEntryBase value) { lock (_ioLock) { return Entries.TryGetValue(key, out value); } } [Obsolete("Use Values instead")] public ConfigEntryBase[] GetConfigEntries() { lock (_ioLock) { return Entries.Values.ToArray(); } } public void Reload() { lock (_ioLock) { OrphanedEntries.Clear(); string section = string.Empty; string[] array = File.ReadAllLines(ConfigFilePath); for (int i = 0; i < array.Length; i++) { string text = array[i].Trim(); if (text.StartsWith("#")) { continue; } if (text.StartsWith("[") && text.EndsWith("]")) { section = text.Substring(1, text.Length - 2); continue; } string[] array2 = text.Split(new char[1] { '=' }, 2); if (array2.Length == 2) { string key = array2[0].Trim(); string text2 = array2[1].Trim(); ConfigDefinition key2 = new ConfigDefinition(section, key); Entries.TryGetValue(key2, out var value); if (value != null) { value.SetSerializedValue(text2); } else { OrphanedEntries[key2] = text2; } } } } OnConfigReloaded(); } public void Save() { lock (_ioLock) { string directoryName = Path.GetDirectoryName(ConfigFilePath); if (directoryName != null) { Directory.CreateDirectory(directoryName); } using StreamWriter streamWriter = new StreamWriter(ConfigFilePath, append: false, Utility.UTF8NoBom); if (_ownerMetadata != null) { streamWriter.WriteLine($"## Settings file was created by plugin {_ownerMetadata.Name} v{_ownerMetadata.Version}"); streamWriter.WriteLine("## Plugin GUID: " + _ownerMetadata.GUID); streamWriter.WriteLine(); } foreach (var item in from x in Entries.Select((KeyValuePair x) => new { Key = x.Key, entry = x.Value, value = x.Value.GetSerializedValue() }).Concat(OrphanedEntries.Select((KeyValuePair x) => new { Key = x.Key, entry = (ConfigEntryBase)null, value = x.Value })) group x by x.Key.Section into x orderby x.Key select x) { streamWriter.WriteLine("[" + item.Key + "]"); foreach (var item2 in item) { if (GenerateSettingDescriptions) { streamWriter.WriteLine(); item2.entry?.WriteDescription(streamWriter); } streamWriter.WriteLine(item2.Key.Key + " = " + item2.value); } streamWriter.WriteLine(); } } } [Obsolete("Use ConfigFile[key] or TryGetEntry instead")] public ConfigEntry GetSetting(ConfigDefinition configDefinition) { if (!TryGetEntry(configDefinition, out ConfigEntry entry)) { return null; } return entry; } [Obsolete("Use ConfigFile[key] or TryGetEntry instead")] public ConfigEntry GetSetting(string section, string key) { if (!TryGetEntry(section, key, out ConfigEntry entry)) { return null; } return entry; } public bool TryGetEntry(ConfigDefinition configDefinition, out ConfigEntry entry) { lock (_ioLock) { if (Entries.TryGetValue(configDefinition, out var value)) { entry = (ConfigEntry)value; return true; } entry = null; return false; } } public bool TryGetEntry(string section, string key, out ConfigEntry entry) { return TryGetEntry(new ConfigDefinition(section, key), out entry); } public ConfigEntry Bind(ConfigDefinition configDefinition, T defaultValue, ConfigDescription configDescription = null) { if (!TomlTypeConverter.CanConvert(typeof(T))) { throw new ArgumentException(string.Format("Type {0} is not supported by the config system. Supported types: {1}", typeof(T), string.Join(", ", (from x in TomlTypeConverter.GetSupportedTypes() select x.Name).ToArray()))); } lock (_ioLock) { if (Entries.TryGetValue(configDefinition, out var value)) { return (ConfigEntry)value; } ConfigEntry configEntry = new ConfigEntry(this, configDefinition, defaultValue, configDescription); Entries[configDefinition] = configEntry; if (OrphanedEntries.TryGetValue(configDefinition, out var value2)) { configEntry.SetSerializedValue(value2); OrphanedEntries.Remove(configDefinition); } if (SaveOnConfigSet) { Save(); } return configEntry; } } public ConfigEntry Bind(string section, string key, T defaultValue, ConfigDescription configDescription = null) { return Bind(new ConfigDefinition(section, key), defaultValue, configDescription); } public ConfigEntry Bind(string section, string key, T defaultValue, string description) { return Bind(new ConfigDefinition(section, key), defaultValue, new ConfigDescription(description, null)); } [Obsolete("Use Bind instead")] public ConfigEntry AddSetting(ConfigDefinition configDefinition, T defaultValue, ConfigDescription configDescription = null) { return Bind(configDefinition, defaultValue, configDescription); } [Obsolete("Use Bind instead")] public ConfigEntry AddSetting(string section, string key, T defaultValue, ConfigDescription configDescription = null) { return Bind(new ConfigDefinition(section, key), defaultValue, configDescription); } [Obsolete("Use Bind instead")] public ConfigEntry AddSetting(string section, string key, T defaultValue, string description) { return Bind(new ConfigDefinition(section, key), defaultValue, new ConfigDescription(description, null)); } [Obsolete("Use Bind instead")] public ConfigWrapper Wrap(string section, string key, string description = null, T defaultValue = default(T)) { lock (_ioLock) { ConfigDefinition configDefinition = new ConfigDefinition(section, key, description); return new ConfigWrapper(Bind(configDefinition, defaultValue, string.IsNullOrEmpty(description) ? null : new ConfigDescription(description, null))); } } [Obsolete("Use Bind instead")] public ConfigWrapper Wrap(ConfigDefinition configDefinition, T defaultValue = default(T)) { return Wrap(configDefinition.Section, configDefinition.Key, null, defaultValue); } internal void OnSettingChanged(object sender, ConfigEntryBase changedEntryBase) { if (changedEntryBase == null) { throw new ArgumentNullException("changedEntryBase"); } if (SaveOnConfigSet) { Save(); } EventHandler settingChanged = this.SettingChanged; if (settingChanged == null) { return; } SettingChangedEventArgs e = new SettingChangedEventArgs(changedEntryBase); Delegate[] invocationList = settingChanged.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { EventHandler eventHandler = (EventHandler)invocationList[i]; try { eventHandler(sender, e); } catch (Exception data) { Logger.Log(LogLevel.Error, data); } } } private void OnConfigReloaded() { EventHandler configReloaded = this.ConfigReloaded; if (configReloaded == null) { return; } Delegate[] invocationList = configReloaded.GetInvocationList(); for (int i = 0; i < invocationList.Length; i++) { EventHandler eventHandler = (EventHandler)invocationList[i]; try { eventHandler(this, EventArgs.Empty); } catch (Exception data) { Logger.Log(LogLevel.Error, data); } } } } [Obsolete("Use ConfigFile from new Bind overloads instead")] public sealed class ConfigWrapper { public ConfigEntry ConfigEntry { get; } public ConfigDefinition Definition => ConfigEntry.Definition; public ConfigFile ConfigFile => ConfigEntry.ConfigFile; public T Value { get { return ConfigEntry.Value; } set { ConfigEntry.Value = value; } } public event EventHandler SettingChanged; internal ConfigWrapper(ConfigEntry configEntry) { ConfigWrapper configWrapper = this; ConfigEntry = configEntry ?? throw new ArgumentNullException("configEntry"); configEntry.ConfigFile.SettingChanged += delegate(object sender, SettingChangedEventArgs args) { if (args.ChangedSetting == configEntry) { configWrapper.SettingChanged?.Invoke(sender, args); } }; } } public sealed class SettingChangedEventArgs : EventArgs { public ConfigEntryBase ChangedSetting { get; } public SettingChangedEventArgs(ConfigEntryBase changedSetting) { ChangedSetting = changedSetting; } } public static class TomlTypeConverter { private static Dictionary TypeConverters { get; } = new Dictionary { [typeof(string)] = new TypeConverter { ConvertToString = (object obj, Type type) => ((string)obj).Escape(), ConvertToObject = (string str, Type type) => Regex.IsMatch(str, "^\"?\\w:\\\\(?!\\\\)(?!.+\\\\\\\\)") ? str : str.Unescape() }, [typeof(bool)] = new TypeConverter { ConvertToString = (object obj, Type type) => obj.ToString().ToLowerInvariant(), ConvertToObject = (string str, Type type) => bool.Parse(str) }, [typeof(byte)] = new TypeConverter { ConvertToString = (object obj, Type type) => obj.ToString(), ConvertToObject = (string str, Type type) => byte.Parse(str) }, [typeof(sbyte)] = new TypeConverter { ConvertToString = (object obj, Type type) => obj.ToString(), ConvertToObject = (string str, Type type) => sbyte.Parse(str) }, [typeof(byte)] = new TypeConverter { ConvertToString = (object obj, Type type) => obj.ToString(), ConvertToObject = (string str, Type type) => byte.Parse(str) }, [typeof(short)] = new TypeConverter { ConvertToString = (object obj, Type type) => obj.ToString(), ConvertToObject = (string str, Type type) => short.Parse(str) }, [typeof(ushort)] = new TypeConverter { ConvertToString = (object obj, Type type) => obj.ToString(), ConvertToObject = (string str, Type type) => ushort.Parse(str) }, [typeof(int)] = new TypeConverter { ConvertToString = (object obj, Type type) => obj.ToString(), ConvertToObject = (string str, Type type) => int.Parse(str) }, [typeof(uint)] = new TypeConverter { ConvertToString = (object obj, Type type) => obj.ToString(), ConvertToObject = (string str, Type type) => uint.Parse(str) }, [typeof(long)] = new TypeConverter { ConvertToString = (object obj, Type type) => obj.ToString(), ConvertToObject = (string str, Type type) => long.Parse(str) }, [typeof(ulong)] = new TypeConverter { ConvertToString = (object obj, Type type) => obj.ToString(), ConvertToObject = (string str, Type type) => ulong.Parse(str) }, [typeof(float)] = new TypeConverter { ConvertToString = (object obj, Type type) => ((float)obj).ToString(NumberFormatInfo.InvariantInfo), ConvertToObject = (string str, Type type) => float.Parse(str, NumberFormatInfo.InvariantInfo) }, [typeof(double)] = new TypeConverter { ConvertToString = (object obj, Type type) => ((double)obj).ToString(NumberFormatInfo.InvariantInfo), ConvertToObject = (string str, Type type) => double.Parse(str, NumberFormatInfo.InvariantInfo) }, [typeof(decimal)] = new TypeConverter { ConvertToString = (object obj, Type type) => ((decimal)obj).ToString(NumberFormatInfo.InvariantInfo), ConvertToObject = (string str, Type type) => decimal.Parse(str, NumberFormatInfo.InvariantInfo) }, [typeof(Enum)] = new TypeConverter { ConvertToString = (object obj, Type type) => obj.ToString(), ConvertToObject = (string str, Type type) => Enum.Parse(type, str, ignoreCase: true) } }; public static string ConvertToString(object value, Type valueType) { return (GetConverter(valueType) ?? throw new InvalidOperationException($"Cannot convert from type {valueType}")).ConvertToString(value, valueType); } public static T ConvertToValue(string value) { return (T)ConvertToValue(value, typeof(T)); } public static object ConvertToValue(string value, Type valueType) { return (GetConverter(valueType) ?? throw new InvalidOperationException("Cannot convert to type " + valueType.Name)).ConvertToObject(value, valueType); } public static TypeConverter GetConverter(Type valueType) { if (valueType == null) { throw new ArgumentNullException("valueType"); } if (valueType.IsEnum) { return TypeConverters[typeof(Enum)]; } TypeConverters.TryGetValue(valueType, out var value); return value; } public static bool AddConverter(Type type, TypeConverter converter) { if (type == null) { throw new ArgumentNullException("type"); } if (converter == null) { throw new ArgumentNullException("converter"); } if (CanConvert(type)) { Logger.Log(LogLevel.Warning, "Tried to add a TomlConverter when one already exists for type " + type.FullName); return false; } TypeConverters.Add(type, converter); return true; } public static bool CanConvert(Type type) { return GetConverter(type) != null; } public static IEnumerable GetSupportedTypes() { return TypeConverters.Keys; } private static string Escape(this string txt) { if (string.IsNullOrEmpty(txt)) { return string.Empty; } StringBuilder stringBuilder = new StringBuilder(txt.Length + 2); foreach (char c in txt) { switch (c) { case '\0': stringBuilder.Append("\\0"); break; case '\a': stringBuilder.Append("\\a"); break; case '\b': stringBuilder.Append("\\b"); break; case '\t': stringBuilder.Append("\\t"); break; case '\n': stringBuilder.Append("\\n"); break; case '\v': stringBuilder.Append("\\v"); break; case '\f': stringBuilder.Append("\\f"); break; case '\r': stringBuilder.Append("\\r"); break; case '\'': stringBuilder.Append("\\'"); break; case '\\': stringBuilder.Append("\\"); break; case '"': stringBuilder.Append("\\\""); break; default: stringBuilder.Append(c); break; } } return stringBuilder.ToString(); } private static string Unescape(this string txt) { if (string.IsNullOrEmpty(txt)) { return txt; } StringBuilder stringBuilder = new StringBuilder(txt.Length); int num = 0; while (num < txt.Length) { int num2 = txt.IndexOf('\\', num); if (num2 < 0 || num2 == txt.Length - 1) { num2 = txt.Length; } stringBuilder.Append(txt, num, num2 - num); if (num2 >= txt.Length) { break; } char c = txt[num2 + 1]; switch (c) { case '0': stringBuilder.Append('\0'); break; case 'a': stringBuilder.Append('\a'); break; case 'b': stringBuilder.Append('\b'); break; case 't': stringBuilder.Append('\t'); break; case 'n': stringBuilder.Append('\n'); break; case 'v': stringBuilder.Append('\v'); break; case 'f': stringBuilder.Append('\f'); break; case 'r': stringBuilder.Append('\r'); break; case '\'': stringBuilder.Append('\''); break; case '"': stringBuilder.Append('"'); break; case '\\': stringBuilder.Append('\\'); break; default: stringBuilder.Append('\\').Append(c); break; } num = num2 + 2; } return stringBuilder.ToString(); } } public class TypeConverter { public Func ConvertToString { get; set; } public Func ConvertToObject { get; set; } } } namespace BepInEx.Bootstrap { public abstract class BaseChainloader { protected static readonly string CurrentAssemblyName = Assembly.GetExecutingAssembly().GetName().Name; protected static readonly Version CurrentAssemblyVersion = Assembly.GetExecutingAssembly().GetName().Version; private bool _initialized; private static readonly ConfigEntry ConfigDiskAppend = ConfigFile.CoreConfig.Bind("Logging.Disk", "AppendLog", defaultValue: false, "Appends to the log file instead of overwriting, on game startup."); private static readonly ConfigEntry ConfigDiskLogging = ConfigFile.CoreConfig.Bind("Logging.Disk", "Enabled", defaultValue: true, "Enables writing log messages to disk."); private static readonly ConfigEntry ConfigDiskLoggingDisplayedLevel = ConfigFile.CoreConfig.Bind("Logging.Disk", "LogLevels", LogLevel.Fatal | LogLevel.Error | LogLevel.Warning | LogLevel.Message | LogLevel.Info, "Only displays the specified log levels in the disk log output."); private static readonly ConfigEntry ConfigDiskLoggingInstantFlushing = ConfigFile.CoreConfig.Bind("Logging.Disk", "InstantFlushing", defaultValue: false, new StringBuilder().AppendLine("If true, instantly writes any received log entries to disk.").AppendLine("This incurs a major performance hit if a lot of log messages are being written, however it is really useful for debugging crashes.").ToString()); private static readonly ConfigEntry ConfigDiskLoggingFileLimit = ConfigFile.CoreConfig.Bind("Logging.Disk", "ConcurrentFileLimit", 5, new StringBuilder().AppendLine("The maximum amount of concurrent log files that will be written to disk.").AppendLine("As one log file is used per open game instance, you may find it necessary to increase this limit when debugging multiple instances at the same time.").ToString()); private static Regex allowedGuidRegex { get; } = new Regex("^[a-zA-Z0-9\\._\\-]+$"); protected virtual string ConsoleTitle => $"BepInEx {Paths.BepInExVersion} - {Paths.ProcessName}"; public Dictionary Plugins { get; } = new Dictionary(); public List DependencyErrors { get; } = new List(); public event Action PluginLoaded; public event Action Finished; public static PluginInfo ToPluginInfo(TypeDefinition type, string assemblyLocation) { if (type.IsInterface || type.IsAbstract) { return null; } try { if (!type.IsSubtypeOf(typeof(TPlugin))) { return null; } } catch (AssemblyResolutionException) { return null; } BepInPlugin bepInPlugin = BepInPlugin.FromCecilType(type); bool isEnabled; if (bepInPlugin == null) { LogLevel logLevel = LogLevel.Warning; LogLevel level = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(59, 1, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Skipping over type ["); bepInExLogInterpolatedStringHandler.AppendFormatted(((MemberReference)type).FullName); bepInExLogInterpolatedStringHandler.AppendLiteral("] as no metadata attribute is specified"); } Logger.Log(level, bepInExLogInterpolatedStringHandler); return null; } if (string.IsNullOrEmpty(bepInPlugin.GUID) || !allowedGuidRegex.IsMatch(bepInPlugin.GUID)) { LogLevel logLevel = LogLevel.Warning; LogLevel level2 = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(61, 2, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Skipping type ["); bepInExLogInterpolatedStringHandler.AppendFormatted(((MemberReference)type).FullName); bepInExLogInterpolatedStringHandler.AppendLiteral("] because its GUID ["); bepInExLogInterpolatedStringHandler.AppendFormatted(bepInPlugin.GUID); bepInExLogInterpolatedStringHandler.AppendLiteral("] is of an illegal format."); } Logger.Log(level2, bepInExLogInterpolatedStringHandler); return null; } if (bepInPlugin.Version == (Version)null) { LogLevel logLevel = LogLevel.Warning; LogLevel level3 = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(48, 1, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Skipping type ["); bepInExLogInterpolatedStringHandler.AppendFormatted(((MemberReference)type).FullName); bepInExLogInterpolatedStringHandler.AppendLiteral("] because its version is invalid."); } Logger.Log(level3, bepInExLogInterpolatedStringHandler); return null; } if (bepInPlugin.Name == null) { LogLevel logLevel = LogLevel.Warning; LogLevel level4 = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(42, 1, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Skipping type ["); bepInExLogInterpolatedStringHandler.AppendFormatted(((MemberReference)type).FullName); bepInExLogInterpolatedStringHandler.AppendLiteral("] because its name is null."); } Logger.Log(level4, bepInExLogInterpolatedStringHandler); return null; } List processes = BepInProcess.FromCecilType(type); IEnumerable dependencies = BepInDependency.FromCecilType(type); IEnumerable incompatibilities = BepInIncompatibility.FromCecilType(type); AssemblyNameReference? obj = ((IEnumerable)((MemberReference)type).Module.AssemblyReferences).FirstOrDefault((Func)((AssemblyNameReference reference) => reference.Name == "BepInEx.Core")); Version targettedBepInExVersion = ((obj != null) ? obj.Version : null) ?? new Version(); return new PluginInfo { Metadata = bepInPlugin, Processes = processes, Dependencies = dependencies, Incompatibilities = incompatibilities, TypeName = ((MemberReference)type).FullName, TargettedBepInExVersion = targettedBepInExVersion, Location = assemblyLocation }; } protected static bool HasBepinPlugins(AssemblyDefinition ass) { if (((IEnumerable)ass.MainModule.AssemblyReferences).All((AssemblyNameReference r) => r.Name != CurrentAssemblyName)) { return false; } if (ass.MainModule.GetTypeReferences().All((TypeReference r) => ((MemberReference)r).FullName != typeof(BepInPlugin).FullName)) { return false; } return true; } protected static bool PluginTargetsWrongBepin(PluginInfo pluginInfo) { Version targettedBepInExVersion = pluginInfo.TargettedBepInExVersion; if (targettedBepInExVersion.Major != CurrentAssemblyVersion.Major) { return true; } if (targettedBepInExVersion.Minor > CurrentAssemblyVersion.Minor) { return true; } if (targettedBepInExVersion.Minor < CurrentAssemblyVersion.Minor) { return false; } return targettedBepInExVersion.Build > CurrentAssemblyVersion.Build; } public virtual void Initialize(string gameExePath = null) { if (_initialized) { throw new InvalidOperationException("Chainloader cannot be initialized multiple times"); } if (gameExePath != null) { Paths.SetExecutablePath(gameExePath); } InitializeLoggers(); if (!Directory.Exists(Paths.PluginPath)) { Directory.CreateDirectory(Paths.PluginPath); } if (!Directory.Exists(Paths.PatcherPluginPath)) { Directory.CreateDirectory(Paths.PatcherPluginPath); } _initialized = true; Logger.Log(LogLevel.Message, "Chainloader initialized"); } protected virtual void InitializeLoggers() { if (ConsoleManager.ConsoleEnabled && !ConsoleManager.ConsoleActive) { ConsoleManager.CreateConsole(); } if (ConsoleManager.ConsoleActive) { if (!Logger.Listeners.Any((ILogListener x) => x is ConsoleLogListener)) { Logger.Listeners.Add(new ConsoleLogListener()); } ConsoleManager.SetConsoleTitle(ConsoleTitle); } if (ConfigDiskLogging.Value) { Logger.Listeners.Add(new DiskLogListener("LogOutput.log", ConfigDiskLoggingDisplayedLevel.Value, ConfigDiskAppend.Value, ConfigDiskLoggingInstantFlushing.Value, ConfigDiskLoggingFileLimit.Value)); } if (!TraceLogSource.IsListening) { Logger.Sources.Add(TraceLogSource.CreateSource()); } if (!Logger.Sources.Any((ILogSource x) => x is HarmonyLogSource)) { Logger.Sources.Add(new HarmonyLogSource()); } } protected IList DiscoverPluginsFrom(string path, string cacheName = "chainloader") { return TypeLoader.FindPluginTypes(path, ToPluginInfo, HasBepinPlugins, cacheName).SelectMany((KeyValuePair> p) => p.Value).ToList(); } protected virtual IList DiscoverPlugins() { return DiscoverPluginsFrom(Paths.PluginPath); } protected virtual IList ModifyLoadOrder(IList plugins) { SortedDictionary> dependencyDict = new SortedDictionary>(StringComparer.InvariantCultureIgnoreCase); Dictionary pluginsByGuid = new Dictionary(); foreach (IGrouping item in from info in plugins group info by info.Metadata.GUID) { bool isEnabled; if (Plugins.TryGetValue(item.Key, out var value)) { LogLevel logLevel = LogLevel.Warning; LogLevel level = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(78, 2, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Skipping ["); bepInExLogInterpolatedStringHandler.AppendFormatted(item.Key); bepInExLogInterpolatedStringHandler.AppendLiteral("] because a plugin with a similar GUID (["); bepInExLogInterpolatedStringHandler.AppendFormatted(value); bepInExLogInterpolatedStringHandler.AppendLiteral("]) has been already loaded."); } Logger.Log(level, bepInExLogInterpolatedStringHandler); continue; } PluginInfo pluginInfo = null; foreach (PluginInfo item2 in item.OrderByDescending((PluginInfo x) => x.Metadata.Version)) { if (pluginInfo != null) { LogLevel logLevel = LogLevel.Warning; LogLevel level2 = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(45, 2, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Skipping ["); bepInExLogInterpolatedStringHandler.AppendFormatted(item2); bepInExLogInterpolatedStringHandler.AppendLiteral("] because a newer version exists ("); bepInExLogInterpolatedStringHandler.AppendFormatted(pluginInfo); bepInExLogInterpolatedStringHandler.AppendLiteral(")"); } Logger.Log(level2, bepInExLogInterpolatedStringHandler); continue; } List list = item2.Processes.ToList(); if (list.Count != 0 && list.All((BepInProcess x) => !string.Equals(x.ProcessName.Replace(".exe", ""), Paths.ProcessName, StringComparison.InvariantCultureIgnoreCase))) { LogLevel logLevel = LogLevel.Warning; LogLevel level3 = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(41, 2, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Skipping ["); bepInExLogInterpolatedStringHandler.AppendFormatted(item2); bepInExLogInterpolatedStringHandler.AppendLiteral("] because of process filters ("); bepInExLogInterpolatedStringHandler.AppendFormatted(string.Join(", ", item2.Processes.Select((BepInProcess p) => p.ProcessName).ToArray())); bepInExLogInterpolatedStringHandler.AppendLiteral(")"); } Logger.Log(level3, bepInExLogInterpolatedStringHandler); } else { pluginInfo = item2; dependencyDict[item2.Metadata.GUID] = item2.Dependencies.Select((BepInDependency d) => d.DependencyGUID); pluginsByGuid[item2.Metadata.GUID] = item2; } } } foreach (PluginInfo item3 in pluginsByGuid.Values.ToList()) { if (item3.Incompatibilities.Any((BepInIncompatibility incompatibility) => pluginsByGuid.ContainsKey(incompatibility.IncompatibilityGUID) || Plugins.ContainsKey(incompatibility.IncompatibilityGUID))) { pluginsByGuid.Remove(item3.Metadata.GUID); dependencyDict.Remove(item3.Metadata.GUID); IEnumerable first = from x in item3.Incompatibilities select x.IncompatibilityGUID into x where pluginsByGuid.ContainsKey(x) select x; IEnumerable second = from x in item3.Incompatibilities select x.IncompatibilityGUID into x where Plugins.ContainsKey(x) select x; string[] value2 = first.Concat(second).ToArray(); string text = string.Format("Could not load [{0}] because it is incompatible with: {1}", item3, string.Join(", ", value2)); DependencyErrors.Add(text); Logger.Log(LogLevel.Error, text); } else if (PluginTargetsWrongBepin(item3)) { string text2 = $"Plugin [{item3}] targets a wrong version of BepInEx ({item3.TargettedBepInExVersion}) and might not work until you update"; DependencyErrors.Add(text2); Logger.Log(LogLevel.Warning, text2); } } string[] emptyDependencies = new string[0]; IEnumerable value3; return (from x in Utility.TopologicalSort(dependencyDict.Keys, (string x) => (!dependencyDict.TryGetValue(x, out value3)) ? emptyDependencies : value3).ToList().Where(pluginsByGuid.ContainsKey) select pluginsByGuid[x]).ToList(); } public virtual void Execute() { bool isEnabled; try { IList list = DiscoverPlugins(); LogLevel logLevel = LogLevel.Info; LogLevel level = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(15, 2, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendFormatted(list.Count); bepInExLogInterpolatedStringHandler.AppendLiteral(" plugin"); bepInExLogInterpolatedStringHandler.AppendFormatted((list.Count == 1) ? "" : "s"); bepInExLogInterpolatedStringHandler.AppendLiteral(" to load"); } Logger.Log(level, bepInExLogInterpolatedStringHandler); LoadPlugins(list); this.Finished?.Invoke(); } catch (Exception t) { try { ConsoleManager.CreateConsole(); } catch { } LogLevel logLevel = LogLevel.Error; LogLevel level2 = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(32, 1, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Error occurred loading plugins: "); bepInExLogInterpolatedStringHandler.AppendFormatted(t); } Logger.Log(level2, bepInExLogInterpolatedStringHandler); } Logger.Log(LogLevel.Message, "Chainloader startup complete"); } private IList LoadPlugins(IList plugins) { IList list = ModifyLoadOrder(plugins); HashSet hashSet = new HashSet(); Dictionary dictionary = new Dictionary(); Dictionary dictionary2 = new Dictionary(); List list2 = new List(); foreach (PluginInfo item in list) { bool flag = false; List list3 = new List(); foreach (BepInDependency dependency in item.Dependencies) { Version value; bool flag2 = dictionary.TryGetValue(dependency.DependencyGUID, out value); if (!flag2) { flag2 = Plugins.TryGetValue(dependency.DependencyGUID, out var value2); value = value2?.Metadata.Version; } if (!flag2 || (dependency.VersionRange != (Range)null && !dependency.VersionRange.IsSatisfied(value, false))) { if (IsHardDependency(dependency)) { list3.Add(dependency); } } else if (hashSet.Contains(dependency.DependencyGUID) && IsHardDependency(dependency)) { flag = true; break; } } dictionary.Add(item.Metadata.GUID, item.Metadata.Version); if (flag) { string text = $"Skipping [{item}] because it has a dependency that was not loaded. See previous errors for details."; DependencyErrors.Add(text); Logger.Log(LogLevel.Warning, text); continue; } if (list3.Count != 0) { string text2 = string.Format("Could not load [{0}] because it has missing dependencies: {1}", item, string.Join(", ", list3.Select((BepInDependency s) => (!(s.VersionRange == (Range)null)) ? $"{s.DependencyGUID} ({s.VersionRange})" : s.DependencyGUID).ToArray())); DependencyErrors.Add(text2); Logger.Log(LogLevel.Error, text2); hashSet.Add(item.Metadata.GUID); continue; } bool isEnabled; try { LogLevel logLevel = LogLevel.Info; LogLevel level = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(10, 1, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Loading ["); bepInExLogInterpolatedStringHandler.AppendFormatted(item); bepInExLogInterpolatedStringHandler.AppendLiteral("]"); } Logger.Log(level, bepInExLogInterpolatedStringHandler); if (!dictionary2.TryGetValue(item.Location, out var value3)) { value3 = (dictionary2[item.Location] = Assembly.LoadFrom(item.Location)); } Plugins[item.Metadata.GUID] = item; TryRunModuleCtor(item, value3); item.Instance = LoadPlugin(item, value3); list2.Add(item); this.PluginLoaded?.Invoke(item); } catch (Exception ex) { hashSet.Add(item.Metadata.GUID); Plugins.Remove(item.Metadata.GUID); LogLevel logLevel = LogLevel.Error; LogLevel level2 = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(18, 2, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Error loading ["); bepInExLogInterpolatedStringHandler.AppendFormatted(item); bepInExLogInterpolatedStringHandler.AppendLiteral("]: "); bepInExLogInterpolatedStringHandler.AppendFormatted((ex is ReflectionTypeLoadException ex2) ? TypeLoader.TypeLoadExceptionToString(ex2) : ex.ToString()); } Logger.Log(level2, bepInExLogInterpolatedStringHandler); } } return list2; static bool IsHardDependency(BepInDependency dep) { return (dep.Flags & BepInDependency.DependencyFlags.HardDependency) != 0; } } public IList LoadPlugins(params string[] pluginsPaths) { List list = new List(); foreach (string path in pluginsPaths) { list.AddRange(DiscoverPluginsFrom(path)); } return LoadPlugins(list); } private static void TryRunModuleCtor(PluginInfo plugin, Assembly assembly) { try { RuntimeHelpers.RunModuleConstructor(assembly.GetType(plugin.TypeName).Module.ModuleHandle); } catch (Exception t) { LogLevel logLevel = LogLevel.Warning; bool isEnabled; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(40, 3, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Couldn't run Module constructor for "); bepInExLogInterpolatedStringHandler.AppendFormatted(assembly.FullName); bepInExLogInterpolatedStringHandler.AppendLiteral("::"); bepInExLogInterpolatedStringHandler.AppendFormatted(plugin.TypeName); bepInExLogInterpolatedStringHandler.AppendLiteral(": "); bepInExLogInterpolatedStringHandler.AppendFormatted(t); } Logger.Log(logLevel, bepInExLogInterpolatedStringHandler); } } public abstract TPlugin LoadPlugin(PluginInfo pluginInfo, Assembly pluginAssembly); } public interface ICacheable { void Save(BinaryWriter bw); void Load(BinaryReader br); } public class CachedAssembly where T : ICacheable { public List CacheItems { get; set; } public string Hash { get; set; } } public static class TypeLoader { public static readonly DefaultAssemblyResolver CecilResolver; public static readonly ReaderParameters ReaderParameters; public static HashSet SearchDirectories; private static readonly ConfigEntry EnableAssemblyCache; [CompilerGenerated] private static AssemblyResolveEventHandler m_AssemblyResolve; public static event AssemblyResolveEventHandler AssemblyResolve { [CompilerGenerated] add { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown AssemblyResolveEventHandler val = TypeLoader.m_AssemblyResolve; AssemblyResolveEventHandler val2; do { val2 = val; AssemblyResolveEventHandler value2 = (AssemblyResolveEventHandler)Delegate.Combine((Delegate?)(object)val2, (Delegate?)(object)value); val = Interlocked.CompareExchange(ref TypeLoader.m_AssemblyResolve, value2, val2); } while (val != val2); } [CompilerGenerated] remove { //IL_000f: Unknown result type (might be due to invalid IL or missing references) //IL_0015: Expected O, but got Unknown AssemblyResolveEventHandler val = TypeLoader.m_AssemblyResolve; AssemblyResolveEventHandler val2; do { val2 = val; AssemblyResolveEventHandler value2 = (AssemblyResolveEventHandler)Delegate.Remove((Delegate?)(object)val2, (Delegate?)(object)value); val = Interlocked.CompareExchange(ref TypeLoader.m_AssemblyResolve, value2, val2); } while (val != val2); } } static TypeLoader() { //IL_0029: Unknown result type (might be due to invalid IL or missing references) //IL_0033: Expected O, but got Unknown //IL_0033: Unknown result type (might be due to invalid IL or missing references) //IL_0038: Unknown result type (might be due to invalid IL or missing references) //IL_0048: Expected O, but got Unknown //IL_0054: Unknown result type (might be due to invalid IL or missing references) //IL_005e: Expected O, but got Unknown SearchDirectories = new HashSet(); EnableAssemblyCache = ConfigFile.CoreConfig.Bind("Caching", "EnableAssemblyCache", defaultValue: true, "Enable/disable assembly metadata cache\nEnabling this will speed up discovery of plugins and patchers by caching the metadata of all types BepInEx discovers."); CecilResolver = new DefaultAssemblyResolver(); ReaderParameters = new ReaderParameters { AssemblyResolver = (IAssemblyResolver)(object)CecilResolver }; ((BaseAssemblyResolver)CecilResolver).ResolveFailure += new AssemblyResolveEventHandler(CecilResolveOnFailure); } public static AssemblyDefinition CecilResolveOnFailure(object sender, AssemblyNameReference reference) { if (!Utility.TryParseAssemblyName(reference.FullName, out var assemblyName)) { return null; } foreach (string item in new string[4] { Paths.BepInExAssemblyDirectory, Paths.PluginPath, Paths.PatcherPluginPath, Paths.ManagedPath }.Concat(SearchDirectories)) { AssemblyDefinition assembly; if (!Directory.Exists(item)) { LogLevel logLevel = LogLevel.Debug; bool isEnabled; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(43, 1, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Unable to resolve cecil search directory '"); bepInExLogInterpolatedStringHandler.AppendFormatted(item); bepInExLogInterpolatedStringHandler.AppendLiteral("'"); } Logger.Log(logLevel, bepInExLogInterpolatedStringHandler); } else if (Utility.TryResolveDllAssembly(assemblyName, item, ReaderParameters, out assembly)) { return assembly; } } AssemblyResolveEventHandler assemblyResolve = TypeLoader.AssemblyResolve; if (assemblyResolve == null) { return null; } return assemblyResolve.Invoke(sender, reference); } public static Dictionary> FindPluginTypes(string directory, Func typeSelector, Func assemblyFilter = null, string cacheName = null) where T : ICacheable, new() { Dictionary> dictionary = new Dictionary>(); Dictionary dictionary2 = new Dictionary(); Dictionary> dictionary3 = null; if (cacheName != null) { dictionary3 = LoadAssemblyCache(cacheName); } string[] files = Directory.GetFiles(Path.GetFullPath(directory), "*.dll", SearchOption.AllDirectories); foreach (string dll in files) { bool isEnabled; try { using MemoryStream memoryStream = new MemoryStream(File.ReadAllBytes(dll)); string text = Utility.HashStream(memoryStream); dictionary2[dll] = text; memoryStream.Position = 0L; if (dictionary3 != null && dictionary3.TryGetValue(dll, out var value) && text == value.Hash) { dictionary[dll] = value.CacheItems; continue; } AssemblyDefinition val = AssemblyDefinition.ReadAssembly((Stream)memoryStream, ReaderParameters); try { LogLevel logLevel = LogLevel.Debug; LogLevel level = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(12, 1, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Examining '"); bepInExLogInterpolatedStringHandler.AppendFormatted(dll); bepInExLogInterpolatedStringHandler.AppendLiteral("'"); } Logger.Log(level, bepInExLogInterpolatedStringHandler); if (assemblyFilter != null && !assemblyFilter(val)) { dictionary[dll] = new List(); continue; } List value2 = (from t in (IEnumerable)val.MainModule.Types select typeSelector(t, dll) into t where t != null select t).ToList(); dictionary[dll] = value2; } finally { ((IDisposable)val)?.Dispose(); } } catch (BadImageFormatException ex) { LogLevel logLevel = LogLevel.Debug; LogLevel level2 = logLevel; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(70, 2, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Skipping loading "); bepInExLogInterpolatedStringHandler.AppendFormatted(dll); bepInExLogInterpolatedStringHandler.AppendLiteral(" because it's not a valid .NET assembly. Full error: "); bepInExLogInterpolatedStringHandler.AppendFormatted(ex.Message); } Logger.Log(level2, bepInExLogInterpolatedStringHandler); } catch (Exception data) { Logger.Log(LogLevel.Error, data); } } if (cacheName != null) { SaveAssemblyCache(cacheName, dictionary, dictionary2); } return dictionary; } public static Dictionary> LoadAssemblyCache(string cacheName) where T : ICacheable, new() { if (!EnableAssemblyCache.Value) { return null; } Dictionary> dictionary = new Dictionary>(); try { string path = Path.Combine(Paths.CachePath, cacheName + "_typeloader.dat"); if (!File.Exists(path)) { return null; } using BinaryReader binaryReader = new BinaryReader(File.OpenRead(path)); int num = binaryReader.ReadInt32(); for (int i = 0; i < num; i++) { string key = binaryReader.ReadString(); string hash = binaryReader.ReadString(); int num2 = binaryReader.ReadInt32(); List list = new List(); for (int j = 0; j < num2; j++) { T item = new T(); item.Load(binaryReader); list.Add(item); } dictionary[key] = new CachedAssembly { Hash = hash, CacheItems = list }; } } catch (Exception ex) { LogLevel logLevel = LogLevel.Warning; bool isEnabled; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(58, 2, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Failed to load cache \""); bepInExLogInterpolatedStringHandler.AppendFormatted(cacheName); bepInExLogInterpolatedStringHandler.AppendLiteral("\"; skipping loading cache. Reason: "); bepInExLogInterpolatedStringHandler.AppendFormatted(ex.Message); bepInExLogInterpolatedStringHandler.AppendLiteral("."); } Logger.Log(logLevel, bepInExLogInterpolatedStringHandler); } return dictionary; } public static void SaveAssemblyCache(string cacheName, Dictionary> entries, Dictionary hashes) where T : ICacheable { if (!EnableAssemblyCache.Value) { return; } try { if (!Directory.Exists(Paths.CachePath)) { Directory.CreateDirectory(Paths.CachePath); } using BinaryWriter binaryWriter = new BinaryWriter(File.OpenWrite(Path.Combine(Paths.CachePath, cacheName + "_typeloader.dat"))); binaryWriter.Write(entries.Count); foreach (KeyValuePair> entry in entries) { binaryWriter.Write(entry.Key); binaryWriter.Write(hashes.TryGetValue(entry.Key, out var value) ? value : ""); binaryWriter.Write(entry.Value.Count); foreach (T item in entry.Value) { item.Save(binaryWriter); } } } catch (Exception ex) { LogLevel logLevel = LogLevel.Warning; bool isEnabled; BepInExLogInterpolatedStringHandler bepInExLogInterpolatedStringHandler = new BepInExLogInterpolatedStringHandler(57, 2, logLevel, out isEnabled); if (isEnabled) { bepInExLogInterpolatedStringHandler.AppendLiteral("Failed to save cache \""); bepInExLogInterpolatedStringHandler.AppendFormatted(cacheName); bepInExLogInterpolatedStringHandler.AppendLiteral("\"; skipping saving cache. Reason: "); bepInExLogInterpolatedStringHandler.AppendFormatted(ex.Message); bepInExLogInterpolatedStringHandler.AppendLiteral("."); } Logger.Log(logLevel, bepInExLogInterpolatedStringHandler); } } public static string TypeLoadExceptionToString(ReflectionTypeLoadException ex) { StringBuilder stringBuilder = new StringBuilder(); Exception[] loaderExceptions = ex.LoaderExceptions; foreach (Exception ex2 in loaderExceptions) { stringBuilder.AppendLine(ex2.Message); if (ex2 is FileNotFoundException ex3) { if (!string.IsNullOrEmpty(ex3.FusionLog)) { stringBuilder.AppendLine("Fusion Log:"); stringBuilder.AppendLine(ex3.FusionLog); } } else if (ex2 is FileLoadException ex4 && !string.IsNullOrEmpty(ex4.FusionLog)) { stringBuilder.AppendLine("Fusion Log:"); stringBuilder.AppendLine(ex4.FusionLog); } stringBuilder.AppendLine(); } return stringBuilder.ToString(); } } }