Decompiled source of BepInEx SplashScreen v2.2.1

patchers/BepInEx.SplashScreen/BepInEx.SplashScreen.Patcher.BepInEx5.dll

Decompiled 6 months ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Timers;
using BepInEx.Configuration;
using BepInEx.Logging;
using HarmonyLib;
using Mono.Cecil;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: AssemblyTitle("BepInEx.SplashScreen.Patcher")]
[assembly: AssemblyDescription("Splash screen that shows loading progress when a game patched with BepInEx is loading.")]
[assembly: AssemblyCompany("https://github.com/BepInEx/BepInEx.SplashScreen")]
[assembly: AssemblyProduct("BepInEx.SplashScreen")]
[assembly: AssemblyCopyright("Copyright ©  2023")]
[assembly: ComVisible(false)]
[assembly: Guid("449b9f51-bbe5-4d4a-8936-e0a3081d79cb")]
[assembly: AssemblyVersion("2.2.0.0")]
namespace BepInEx.SplashScreen;

public static class BepInExSplashScreenPatcher
{
	public const string Version = "1.0";

	private static int _initialized;

	public static IEnumerable<string> TargetDLLs
	{
		get
		{
			Init();
			return Enumerable.Empty<string>();
		}
	}

	static BepInExSplashScreenPatcher()
	{
		Init();
	}

	public static void Patch(AssemblyDefinition _)
	{
		Init();
	}

	public static void Init()
	{
		if (Interlocked.Exchange(ref _initialized, 1) != 1)
		{
			SplashScreenController.SpawnSplash();
		}
	}
}
internal class Metadata
{
	public const string Version = "2.2";
}
internal sealed class LoadingLogListener : ILogListener, IDisposable
{
	private bool _disposed;

	private LoadingLogListener()
	{
	}

	public static LoadingLogListener StartListening()
	{
		LoadingLogListener loadingLogListener = new LoadingLogListener();
		Logger.Listeners.Add((ILogListener)(object)loadingLogListener);
		return loadingLogListener;
	}

	public void Dispose()
	{
		_disposed = true;
	}

	public void LogEvent(object sender, LogEventArgs eventArgs)
	{
		if (_disposed || !(eventArgs.Source.SourceName == "BepInEx") || eventArgs.Data == null)
		{
			return;
		}
		try
		{
			string text = (eventArgs.Data as string) ?? eventArgs.Data.ToString();
			if (text == "Chainloader startup complete")
			{
				Dispose();
				Traverse threadingHelper = Traverse.CreateWithType("BepInEx.ThreadingHelper").Property("Instance", (object[])null);
				Traverse val = threadingHelper.Method("StartSyncInvoke", new Type[1] { typeof(Action) }, (object[])null);
				if (val.MethodExists())
				{
					val.GetValue(new object[1] { (Action)delegate
					{
						try
						{
							threadingHelper.Method("StartCoroutine", new Type[1] { typeof(IEnumerator) }, (object[])null).GetValue(new object[1] { DelayedCo() });
						}
						catch (Exception ex2)
						{
							SplashScreenController.Logger.LogError((object)("Unexpected crash when trying to StartCoroutine: " + ex2));
							SplashScreenController.KillSplash();
						}
					} });
				}
				else
				{
					Process currentProcess = Process.GetCurrentProcess();
					System.Timers.Timer timer = new System.Timers.Timer(500.0);
					timer.Elapsed += delegate
					{
						try
						{
							timer.Stop();
							currentProcess.Refresh();
							if (currentProcess.Responding)
							{
								timer.Dispose();
								SplashScreenController.KillSplash();
							}
							else
							{
								SplashScreenController.Logger.LogDebug((object)"Process not responding, waiting...");
								timer.Start();
							}
						}
						catch (Exception ex)
						{
							SplashScreenController.Logger.LogError((object)("Unexpected crash in timer.Elapsed: " + ex));
							SplashScreenController.KillSplash();
						}
					};
					timer.AutoReset = false;
					timer.Start();
				}
			}
			else if (text.StartsWith("Failed to run [BepInEx.Chainloader]", StringComparison.Ordinal) || string.Equals(text, "Could not run preloader!", StringComparison.Ordinal))
			{
				Dispose();
				SplashScreenController.KillSplash();
			}
			SplashScreenController.SendMessage(text);
		}
		catch (Exception arg)
		{
			SplashScreenController.Logger.LogError((object)string.Format("Crash in {0}, aborting. Exception: {1}", "LogEvent", arg));
			SplashScreenController.KillSplash();
		}
	}

	private static IEnumerator DelayedCo()
	{
		for (int i = 0; i < 1; i++)
		{
			yield return null;
		}
		SplashScreenController.KillSplash();
	}
}
public static class SplashScreenController
{
	internal static readonly ManualLogSource Logger = Logger.CreateLogSource("Splash");

	private static readonly Queue _StatusQueue = Queue.Synchronized(new Queue(10, 2f));

	private static LoadingLogListener _logListener;

	private static Process _guiProcess;

	public static void SpawnSplash()
	{
		//IL_001b: Unknown result type (might be due to invalid IL or missing references)
		//IL_0021: Expected O, but got Unknown
		try
		{
			ConfigFile val = (ConfigFile)AccessTools.Property(typeof(ConfigFile), "CoreConfig").GetValue(null, null);
			bool value = val.Bind<bool>("SplashScreen", "Enabled", true, "Display a splash screen with information about game load progress on game start-up.").Value;
			bool value2 = val.Bind<bool>("SplashScreen", "OnlyNoConsole", true, "Only display the splash screen if the logging console is turned off.").Value;
			if (!value)
			{
				Logger.LogDebug((object)"Not showing splash because the Enabled setting is off");
				return;
			}
			ConfigEntry<bool> val2 = default(ConfigEntry<bool>);
			if (value2 && val.TryGetEntry<bool>("Logging.Console", "Enabled", ref val2) && val2.Value)
			{
				Logger.LogDebug((object)"Not showing splash because the console is enabled");
				return;
			}
			string text = Path.Combine(Path.GetDirectoryName(typeof(SplashScreenController).Assembly.Location) ?? Paths.PatcherPluginPath, "BepInEx.SplashScreen.GUI.exe");
			if (!File.Exists(text))
			{
				throw new FileNotFoundException("Executable not found or inaccessible at " + text);
			}
			Logger.Log((LogLevel)32, (object)("Starting GUI process: " + text));
			ProcessStartInfo startInfo = new ProcessStartInfo(text, Process.GetCurrentProcess().Id.ToString())
			{
				UseShellExecute = false,
				RedirectStandardInput = true,
				RedirectStandardOutput = true,
				RedirectStandardError = true,
				StandardOutputEncoding = Encoding.UTF8,
				StandardErrorEncoding = Encoding.UTF8
			};
			_guiProcess = Process.Start(startInfo);
			Thread thread = new Thread(CommunicationThread);
			thread.IsBackground = true;
			thread.Start(_guiProcess);
			_logListener = LoadingLogListener.StartListening();
		}
		catch (Exception ex)
		{
			Logger.LogError((object)("Failed to start GUI: " + ex));
			KillSplash();
		}
	}

	internal static void SendMessage(string message)
	{
		_StatusQueue.Enqueue(message);
	}

	private static void CommunicationThread(object processArg)
	{
		try
		{
			Process process = (Process)processArg;
			process.Exited += delegate
			{
				KillSplash();
			};
			process.OutputDataReceived += delegate(object sender, DataReceivedEventArgs args)
			{
				if (args.Data != null)
				{
					Logger.Log((LogLevel)32, (object)("[GUI] " + args.Data.Replace('\t', '\n').TrimEnd(new char[1] { '\n' })));
				}
			};
			process.BeginOutputReadLine();
			process.ErrorDataReceived += delegate(object sender, DataReceivedEventArgs args)
			{
				if (args.Data != null)
				{
					Logger.Log((LogLevel)2, (object)("[GUI] " + args.Data.Replace('\t', '\n').TrimEnd(new char[1] { '\n' })));
				}
			};
			process.BeginErrorReadLine();
			process.StandardInput.AutoFlush = false;
			Logger.LogDebug((object)"Connected to the GUI process");
			bool flag = false;
			while (!process.HasExited)
			{
				while (_StatusQueue.Count > 0 && process.StandardInput.BaseStream.CanWrite)
				{
					process.StandardInput.WriteLine(_StatusQueue.Dequeue());
					flag = true;
				}
				if (flag)
				{
					flag = false;
					process.StandardInput.Flush();
				}
				Thread.Sleep(150);
			}
		}
		catch (ThreadAbortException)
		{
		}
		catch (Exception arg)
		{
			Logger.LogError((object)string.Format("Crash in {0}, aborting. Exception: {1}", "CommunicationThread", arg));
		}
		finally
		{
			KillSplash();
		}
	}

	internal static void KillSplash()
	{
		try
		{
			_logListener?.Dispose();
			_StatusQueue.Clear();
			_StatusQueue.TrimToSize();
			try
			{
				if (_guiProcess != null && !_guiProcess.HasExited)
				{
					Logger.LogDebug((object)"Closing GUI process");
					_guiProcess.Kill();
				}
			}
			catch (Exception)
			{
			}
			Logger.Dispose();
		}
		catch (Exception value)
		{
			Console.WriteLine(value);
		}
	}
}