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_0012: Unknown result type (might be due to invalid IL or missing references)
//IL_0018: Expected O, but got Unknown
try
{
ConfigFile val = new ConfigFile(Path.Combine(Paths.ConfigPath, "Bertogim.LoadingScreen.cfg"), true);
bool value = val.Bind<bool>("LoadingScreen", "Enabled", true, "Display a loading window with information about game load progress on game start-up.").Value;
string value2 = val.Bind<string>("LoadingScreen", "WindowType", "FakeGame", "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).").Value;
int value3 = val.Bind<int>("LoadingScreen", "ExtraWaitTime", 1, "Seconds extra to mantain the loading screen starting when the game window shows up \nGood for big modpacks where the lethal company window stays blank loading for a few seconds").Value;
int value4 = val.Bind<int>("LoadingScreen", "WindowWidth", 640, "The window width in pixels (Gets affected by windows screen scale config) \nHeight is automatically calculated by the image aspect ratio.").Value;
if (!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, "LoadingScreen"), "LoadingScreen.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);
}
}
}