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);
}
}
}