Decompiled source of LoadingScreen v1.1.4

BepInEx/patchers/Bertogim-LoadingScreen/BepInEx.SplashScreen.Patcher.BepInEx5.dll

Decompiled 2 weeks 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.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[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.1.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.1";
}
internal sealed class LoadingLogListener : ILogListener, IDisposable
{
	[CompilerGenerated]
	private sealed class <DelayedCo>d__5 : IEnumerator<object>, IDisposable, IEnumerator
	{
		private int <>1__state;

		private object <>2__current;

		private int <i>5__1;

		object IEnumerator<object>.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		object IEnumerator.Current
		{
			[DebuggerHidden]
			get
			{
				return <>2__current;
			}
		}

		[DebuggerHidden]
		public <DelayedCo>d__5(int <>1__state)
		{
			this.<>1__state = <>1__state;
		}

		[DebuggerHidden]
		void IDisposable.Dispose()
		{
			<>1__state = -2;
		}

		private bool MoveNext()
		{
			switch (<>1__state)
			{
			default:
				return false;
			case 0:
				<>1__state = -1;
				<i>5__1 = 0;
				break;
			case 1:
				<>1__state = -1;
				<i>5__1++;
				break;
			}
			if (<i>5__1 < 1)
			{
				<>2__current = null;
				<>1__state = 1;
				return true;
			}
			SplashScreenController.KillSplash();
			return false;
		}

		bool IEnumerator.MoveNext()
		{
			//ILSpy generated this explicit interface implementation from .override directive in MoveNext
			return this.MoveNext();
		}

		[DebuggerHidden]
		void IEnumerator.Reset()
		{
			throw new NotSupportedException();
		}
	}

	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()
	{
		//yield-return decompiler failed: Unexpected instruction in Iterator.Dispose()
		return new <DelayedCo>d__5(0);
	}
}
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_0014: Unknown result type (might be due to invalid IL or missing references)
		//IL_001a: Expected O, but got Unknown
		//IL_0067: Unknown result type (might be due to invalid IL or missing references)
		//IL_0071: Expected O, but got Unknown
		//IL_00ac: Unknown result type (might be due to invalid IL or missing references)
		//IL_00b6: Expected O, but got Unknown
		//IL_01fe: Unknown result type (might be due to invalid IL or missing references)
		//IL_0208: Expected O, but got Unknown
		//IL_0246: Unknown result type (might be due to invalid IL or missing references)
		//IL_0250: Expected O, but got Unknown
		//IL_02d3: Unknown result type (might be due to invalid IL or missing references)
		//IL_02dd: Expected O, but got Unknown
		try
		{
			ConfigFile val = new ConfigFile(Path.Combine(Paths.ConfigPath, "Bertogim.LoadingScreen.cfg"), true);
			ConfigEntry<bool> val2 = val.Bind<bool>("1. LoadingScreen", "Enabled", true, "Display a loading window with information about game load progress on game start-up.");
			ConfigEntry<string> val3 = val.Bind<string>("2. Window", "WindowType", "FakeGame", new ConfigDescription("FakeGame = Makes a window with the same icon as the game, tries to mimic the game till it appears\nFixedWindow = A fixed loading screen on top of all windows, cant move or close and is not on the taskbar (Same behavior as v1.0.5 and less) (I wouldn't use this option since it can be annoying to have the loading screen stuck on top of everything).", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[2] { "FakeGame", "FixedWindow" }), new object[0]));
			ConfigEntry<int> val4 = val.Bind<int>("2. Window", "WindowWidth", 640, "The window width in pixels (Gets affected by windows screen scale config) \nHeight is automatically calculated by the image aspect ratio.");
			ConfigEntry<int> val5 = val.Bind<int>("2. Window", "ExtraWaitTime", 1, new ConfigDescription("Seconds extra to maintain the loading screen starting when the game window shows up.\nGood for big modpacks where the game window stays blank loading for a few seconds", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 60), new object[0]));
			ConfigEntry<string> setting2 = val.Bind<string>("2. Window", "TitleBarColor", "FFFFFF", "Hex color for the window's title bar (e.g. 1E90FF for DodgerBlue). Leave as FFFFFF for default behavior. Requires Windows 10 build 1809+");
			ConfigEntry<string> setting3 = val.Bind<string>("2. Window", "BackgroundColor", "000000", "Hex color for the background (Custom images cover this)");
			ConfigEntry<bool> val6 = val.Bind<bool>("2. Window", "RandomizeImage", false, "Whether to randomize the background image on each game start");
			ConfigEntry<string> val7 = val.Bind<string>("2. Window", "ImagePath", "./Plugins/*/LoadingScreen/*.png", "Path to where to get the image or images for the background\n Relative to the BepInEx folder");
			ConfigEntry<bool> val8 = val.Bind<bool>("2. Window", "ImagePathLogs", false, "Enable extra logging about image path finding");
			ConfigEntry<string> setting4 = val.Bind<string>("3. Text", "TextColor", "FFFFFF", "Text color in hex format (e.g. FFFFFF for white).");
			ConfigEntry<string> val9 = val.Bind<string>("3. Text", "TextFont", "Segoe UI", "Font name used for the loading text (e.g. Arial, Segoe UI, Consolas). Must match an installed system font.\nFor a list of default Windows fonts, visit: https://learn.microsoft.com/en-us/typography/fonts/windows_10_font_list");
			ConfigEntry<string> setting5 = val.Bind<string>("3. Text", "TextBackgroundColor", "595959", "Hex color for background behind the text (e.g. 595959 for gray)");
			ConfigEntry<bool> val10 = val.Bind<bool>("4. ProgressBar", "UseCustomProgressBar", true, "Whether to use a customizable progress bar (allows using the options below) instead of the Windows native one");
			ConfigEntry<string> setting6 = val.Bind<string>("4. ProgressBar", "ProgressBarColor", "34b233", "Progress bar color, use a hex color (e.g. 34b233 for Wageningen Green)");
			ConfigEntry<string> setting7 = val.Bind<string>("4. ProgressBar", "ProgressBarBackgroundColor", "FFFFFF", "Progress bar background color, use a hex color (e.g. FFFFFF for white)");
			ConfigEntry<int> val11 = val.Bind<int>("4. ProgressBar", "ProgressBarBorderSize", 0, new ConfigDescription("Border thickness in pixels (0-4)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 4), new object[0]));
			ConfigEntry<string> setting8 = val.Bind<string>("4. ProgressBar", "ProgressBarBorderColor", "FFFFFF", "Border color in hex format (e.g. FF0000)");
			ConfigEntry<int> val12 = val.Bind<int>("4. ProgressBar", "ProgressBarSmoothness", 25, new ConfigDescription("Loading bar smoothness when changing (0-100)", (AcceptableValueBase)(object)new AcceptableValueRange<int>(0, 100), new object[0]));
			ConfigEntry<string> val13 = val.Bind<string>("4. ProgressBar", "ProgressBarCurve", "EaseOut", new ConfigDescription("Animation curve used to interpolate the loading bar value smoothly over time.\nAvailable curves:\n- Linear: Constant speed\n- EaseIn: Starts slow, speeds up\n- EaseOut: Starts fast, slows down\n- EaseInOut: Slow start and end\n- SmootherStep: Smoothest transition\n- Exponential: Very slow start, accelerates\n- Elastic: Overshoots and bounces into place\n- Bounce: Bounces like a ball at the end\n- BackIn: Starts by going backward, then forward\n- BackOut: Overshoots slightly and comes back\n- Spring: Oscillates like a spring", (AcceptableValueBase)(object)new AcceptableValueList<string>(new string[11]
			{
				"Linear", "EaseIn", "EaseOut", "EaseInOut", "SmootherStep", "Exponential", "Elastic", "Bounce", "BackIn", "BackOut",
				"Spring"
			}), new object[0]));
			ConfigEntry<bool> val14 = val.Bind<bool>("5. Other", "GenerateStartupPluginLoadTimeInfo", false, "Generate information about how many time each plugin took to load (File will generate in BepInEx/Patchers/Bertogim-LoadingScreen/Debug)");
			CheckValue(setting2);
			CheckValue(setting3);
			CheckValue(setting4);
			CheckValue(setting5);
			CheckValue(setting6);
			CheckValue(setting7);
			CheckValue(setting8);
			if (!val2.Value)
			{
				Logger.LogDebug((object)"Not showing splash because the Enabled setting is off");
				return;
			}
			string text = Path.Combine(Path.GetDirectoryName(typeof(SplashScreenController).Assembly.Location) ?? Path.Combine(Paths.PatcherPluginPath, "1. LoadingScreen"), "LoadingScreen.GUI.exe");
			if (!File.Exists(text))
			{
				throw new FileNotFoundException("Executable not found or inaccessible at " + text);
			}
			Logger.Log((LogLevel)16, (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();
		}
		static void CheckValue(ConfigEntry<string> setting)
		{
			string text2 = RemoveHashtag(setting.Value);
			if (text2 != setting.Value)
			{
				setting.Value = text2;
			}
		}
		static string RemoveHashtag(string value)
		{
			if (!string.IsNullOrEmpty(value) && value.StartsWith("#"))
			{
				return value.Substring(1);
			}
			return value;
		}
	}

	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)16, (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);
		}
	}
}