Decompiled source of BepInEx GUI v3.0.3

BepInEx/patchers/BepInEx.GUI/BepInEx.GUI.Loader.dll

Decompiled 2 months ago
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using System.Text;
using System.Threading;
using BepInEx.Configuration;
using BepInEx.Logging;
using Microsoft.CodeAnalysis;
using Mono.Cecil;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: TargetFramework(".NETStandard,Version=v2.0", FrameworkDisplayName = ".NET Standard 2.0")]
[assembly: AssemblyCompany("BepInEx.GUI.Loader")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+bba91d0f08bc552e316d40748c77d3e95f9c4405")]
[assembly: AssemblyProduct("BepInEx.GUI.Loader")]
[assembly: AssemblyTitle("BepInEx.GUI.Loader")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Microsoft.CodeAnalysis.Embedded]
	[AttributeUsage(AttributeTargets.Module, AllowMultiple = false, Inherited = false)]
	internal sealed class RefSafetyRulesAttribute : Attribute
	{
		public readonly int Version;

		public RefSafetyRulesAttribute(int P_0)
		{
			Version = P_0;
		}
	}
}
namespace BepInEx.GUI.Loader
{
	public class CloseProcessOnChainloaderDone : ILogListener, IDisposable
	{
		private bool _disposed;

		private Process _process;

		public CloseProcessOnChainloaderDone(Process process)
		{
			_process = process;
		}

		public void Dispose()
		{
			_disposed = true;
		}

		public void LogEvent(object sender, LogEventArgs eventArgs)
		{
			//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)
			if (!_disposed && eventArgs.Data.ToString() == "Chainloader startup complete")
			{
				LogLevel level = eventArgs.Level;
				if (((object)(LogLevel)(ref level)).Equals((object)(LogLevel)8) && Config.CloseWindowWhenGameLoadedConfig.Value)
				{
					Log.Message("Closing BepInEx.GUI");
					KillBepInExGUIProcess();
				}
			}
		}

		private void KillBepInExGUIProcess()
		{
			try
			{
				_process.Kill();
			}
			catch (Exception ex)
			{
				Log.Error($"Error while trying to kill BepInEx GUI Process: {ex}");
				Log.Error(ex);
			}
			finally
			{
				SendLogToClientSocket.Instance.Dispose();
				Dispose();
			}
		}
	}
	internal static class Config
	{
		internal const string FileName = "BepInEx.GUI.cfg";

		internal const string EnableBepInExGUIConfigKey = "Enable BepInEx GUI";

		internal const string EnableBepInExGUIConfigDescription = "Enable the custom BepInEx GUI";

		internal const string CloseWindowWhenGameLoadedConfigKey = "Close Window When Game Loaded";

		internal const string CloseWindowWhenGameLoadedConfigDescription = "Close the graphic user interface window when the game is loaded";

		internal const string CloseWindowWhenGameClosesConfigKey = "Close Window When Game Closes";

		internal const string CloseWindowWhenGameClosesConfigDescription = "Close the graphic user interface window when the game closes";

		internal static string ConfigFilePath { get; private set; }

		private static ConfigFile File { get; set; }

		internal static ConfigEntry<bool> EnableBepInExGUIConfig { get; private set; }

		internal static ConfigEntry<bool> CloseWindowWhenGameLoadedConfig { get; private set; }

		internal static ConfigEntry<bool> CloseWindowWhenGameClosesConfig { get; private set; }

		internal static void Init(string folderFullPath)
		{
			//IL_0016: Unknown result type (might be due to invalid IL or missing references)
			//IL_0020: Expected O, but got Unknown
			ConfigFilePath = Path.Combine(folderFullPath, "BepInEx.GUI.cfg");
			File = new ConfigFile(ConfigFilePath, true);
			EnableBepInExGUIConfig = File.Bind<bool>("Settings", "Enable BepInEx GUI", true, "Enable the custom BepInEx GUI");
			CloseWindowWhenGameLoadedConfig = File.Bind<bool>("Settings", "Close Window When Game Loaded", false, "Close the graphic user interface window when the game is loaded");
			CloseWindowWhenGameClosesConfig = File.Bind<bool>("Settings", "Close Window When Game Closes", true, "Close the graphic user interface window when the game closes");
		}
	}
	internal static class EntryPoint
	{
		public static IEnumerable<string> TargetDLLs { get; } = Array.Empty<string>();


		public static void Patch(AssemblyDefinition _)
		{
		}

		public static void Initialize()
		{
			Log.Init();
			try
			{
				InitializeInternal();
			}
			catch (Exception ex)
			{
				Log.Error($"Failed to initialize : ({ex.GetType()}) {ex.Message}{Environment.NewLine}{ex}");
			}
		}

		private static void InitializeInternal()
		{
			Config.Init(Paths.ConfigPath);
			if (((ConfigEntry<bool>)typeof(BepInPlugin).Assembly.GetType("BepInEx.ConsoleManager", throwOnError: true).GetField("ConfigConsoleEnabled", BindingFlags.Static | BindingFlags.Public).GetValue(null)).Value)
			{
				Log.Info("BepInEx regular console is enabled, aborting launch.");
			}
			else if (Config.EnableBepInExGUIConfig.Value)
			{
				FindAndLaunchGUI();
			}
			else
			{
				Log.Info("Custom BepInEx.GUI is disabled in the config, aborting launch.");
			}
		}

		private static string FindGUIExecutable()
		{
			string[] files = Directory.GetFiles(Paths.PatcherPluginPath, "*", SearchOption.AllDirectories);
			foreach (string text in files)
			{
				if (Path.GetFileName(text) == "bepinex_gui.exe" && FileVersionInfo.GetVersionInfo(text).FileMajorPart == 3)
				{
					Log.Info("Found bepinex_gui v3 executable in " + text);
					return text;
				}
			}
			return null;
		}

		private static void FindAndLaunchGUI()
		{
			Log.Info("Finding and launching GUI");
			string text = FindGUIExecutable();
			if (text != null)
			{
				int num = FindFreePort();
				Process process = LaunchGUI(text, num);
				if (process != null)
				{
					Logger.Listeners.Add((ILogListener)(object)new SendLogToClientSocket(num));
					Logger.Listeners.Add((ILogListener)(object)new CloseProcessOnChainloaderDone(process));
				}
				else
				{
					Log.Info("LaunchGUI failed");
				}
			}
			else
			{
				Log.Info("bepinex_gui executable not found.");
			}
		}

		private static int FindFreePort()
		{
			int num = 0;
			Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
			try
			{
				IPEndPoint localEP = new IPEndPoint(IPAddress.Any, 0);
				socket.Bind(localEP);
				localEP = (IPEndPoint)socket.LocalEndPoint;
				return localEP.Port;
			}
			finally
			{
				socket.Close();
			}
		}

		private static Process LaunchGUI(string executablePath, int socketPort)
		{
			ProcessStartInfo processStartInfo = new ProcessStartInfo();
			processStartInfo.FileName = executablePath;
			processStartInfo.WorkingDirectory = Path.GetDirectoryName(executablePath);
			processStartInfo.Arguments = $"\"{typeof(Paths).Assembly.GetName().Version}\" " + "\"" + Paths.ProcessName + "\" \"" + Paths.GameRootPath + "\" \"" + GetLogOutputFilePath() + "\" \"" + Config.ConfigFilePath + "\" " + $"\"{Process.GetCurrentProcess().Id}\" " + $"\"{socketPort}\"";
			return Process.Start(processStartInfo);
		}

		private static string GetLogOutputFilePath()
		{
			foreach (ILogListener listener in Logger.Listeners)
			{
				DiskLogListener val = (DiskLogListener)(object)((listener is DiskLogListener) ? listener : null);
				if (val != null)
				{
					return val.FileFullPath;
				}
			}
			return "";
		}
	}
	internal static class Log
	{
		private static ManualLogSource _logSource;

		internal static void Init()
		{
			_logSource = Logger.CreateLogSource("BepInEx.GUI.Loader");
		}

		internal static void Debug(object data)
		{
			_logSource.LogDebug(data);
		}

		internal static void Error(object data)
		{
			_logSource.LogError(data);
		}

		internal static void Fatal(object data)
		{
			_logSource.LogFatal(data);
		}

		internal static void Info(object data)
		{
			_logSource.LogInfo(data);
		}

		internal static void Message(object data)
		{
			_logSource.LogMessage(data);
		}

		internal static void Warning(object data)
		{
			_logSource.LogWarning(data);
		}
	}
	internal struct LogPacket
	{
		internal byte[] Bytes;

		internal unsafe LogPacket(LogEventArgs log)
		{
			//IL_0047: Unknown result type (might be due to invalid IL or missing references)
			//IL_004d: Expected I4, but got Unknown
			byte[] bytes = Encoding.UTF8.GetBytes(((object)log).ToString());
			int num = bytes.Length;
			Bytes = new byte[8 + num];
			fixed (byte* ptr = Bytes)
			{
				*(int*)ptr = num;
				*(int*)(ptr + 4) = (int)log.Level;
				Marshal.Copy(bytes, 0, (IntPtr)(ptr + 8), num);
			}
		}
	}
	internal class SendLogToClientSocket : ILogListener, IDisposable
	{
		private int _freePort;

		private readonly Thread _thread;

		private readonly object _queueLock = new object();

		private readonly Queue<LogEventArgs> _logQueue = new Queue<LogEventArgs>();

		private bool _isDisposed;

		private bool _gotFirstLog;

		internal static SendLogToClientSocket Instance { get; private set; }

		internal SendLogToClientSocket(int freePort)
		{
			Instance = this;
			_freePort = freePort;
			_thread = new Thread((ThreadStart)delegate
			{
				TcpListener tcpListener = new TcpListener(IPAddress.Parse("127.0.0.1"), _freePort);
				tcpListener.Start();
				while (true)
				{
					Log.Info("[SendLogToClient] Accepting Socket.");
					Socket clientSocket = tcpListener.AcceptSocket();
					if (_isDisposed)
					{
						break;
					}
					SendPacketsToClientUntilConnectionIsClosed(clientSocket);
				}
			});
			_thread.Start();
		}

		private void SendPacketsToClientUntilConnectionIsClosed(Socket clientSocket)
		{
			while (!_isDisposed)
			{
				while (_logQueue.Count > 0)
				{
					LogEventArgs log;
					lock (_queueLock)
					{
						log = _logQueue.Peek();
					}
					LogPacket logPacket = new LogPacket(log);
					try
					{
						clientSocket.Send(logPacket.Bytes);
					}
					catch (Exception arg)
					{
						Log.Error($"Error while trying to send log to socket: {arg}{Environment.NewLine}Disconnecting socket.");
						return;
					}
					lock (_queueLock)
					{
						_logQueue.Dequeue();
					}
				}
				Thread.Sleep(17);
			}
		}

		public void Dispose()
		{
			_isDisposed = true;
		}

		internal void StoreLog(LogEventArgs eventArgs)
		{
			lock (_queueLock)
			{
				_logQueue.Enqueue(eventArgs);
			}
		}

		public void LogEvent(object sender, LogEventArgs eventArgs)
		{
			//IL_001b: Unknown result type (might be due to invalid IL or missing references)
			//IL_0021: Invalid comparison between Unknown and I4
			if (_isDisposed || eventArgs.Data == null)
			{
				return;
			}
			if (!_gotFirstLog)
			{
				if ((int)eventArgs.Level == 8 && eventArgs.Source.SourceName == "BepInEx" && eventArgs.Data.ToString().StartsWith("BepInEx"))
				{
					_gotFirstLog = true;
				}
			}
			else
			{
				StoreLog(eventArgs);
			}
		}
	}
}