Decompiled source of CustomLoadingScreens v1.1.1

CustomLoadingScreens.dll

Decompiled 14 hours ago
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Resources;
using System.Runtime.CompilerServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using CustomLoadingScreens;
using CustomLoadingScreens.Config;
using CustomLoadingScreens.Integrations;
using CustomLoadingScreens.Integrations.Interop;
using CustomLoadingScreens.LoadingScreen;
using CustomLoadingScreens.Utils;
using HarmonyLib;
using MelonLoader;
using MelonLoader.Preferences;
using MelonLoader.Utils;
using Microsoft.CodeAnalysis;
using S1API.Logging;
using UnityEngine;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.Default | DebuggableAttribute.DebuggingModes.DisableOptimizations | DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints | DebuggableAttribute.DebuggingModes.EnableEditAndContinue)]
[assembly: MelonInfo(typeof(Core), "Custom Loading Screens", "1.1.0", "Riccaforte", null)]
[assembly: MelonGame("TVGS", "Schedule I")]
[assembly: MelonAuthorColor(1, 68, 2, 152)]
[assembly: MelonColor(1, 68, 2, 152)]
[assembly: TargetFramework(".NETStandard,Version=v2.1", FrameworkDisplayName = ".NET Standard 2.1")]
[assembly: AssemblyCompany("CustomLoadingScreens")]
[assembly: AssemblyConfiguration("CrossCompat")]
[assembly: AssemblyFileVersion("1.0.0.0")]
[assembly: AssemblyInformationalVersion("1.0.0+505a2426662e697aa09e6181e7830337d1bc9e02")]
[assembly: AssemblyProduct("CustomLoadingScreens")]
[assembly: AssemblyTitle("CustomLoadingScreens")]
[assembly: NeutralResourcesLanguage("en-US")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.0.0.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Event | AttributeTargets.Parameter | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableAttribute : Attribute
	{
		public readonly byte[] NullableFlags;

		public NullableAttribute(byte P_0)
		{
			NullableFlags = new byte[1] { P_0 };
		}

		public NullableAttribute(byte[] P_0)
		{
			NullableFlags = P_0;
		}
	}
	[CompilerGenerated]
	[Embedded]
	[AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Method | AttributeTargets.Interface | AttributeTargets.Delegate, AllowMultiple = false, Inherited = false)]
	internal sealed class NullableContextAttribute : Attribute
	{
		public readonly byte Flag;

		public NullableContextAttribute(byte P_0)
		{
			Flag = P_0;
		}
	}
	[CompilerGenerated]
	[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 CustomLoadingScreens
{
	public class Core : MelonMod
	{
		private static ModPreferences? _preferences;

		private PreferencesFileWatcher? _preferencesWatcher;

		private ImageFolderWatcher? _imageFolderWatcher;

		private LoadingScreenBackgroundController? _backgroundController;

		public readonly Log Logger = new Log("CustomLoadingScreens");

		public static Core? Instance { get; private set; }

		internal static ModPreferences? Preferences => _preferences;

		public override void OnInitializeMelon()
		{
			Instance = this;
			_preferences = new ModPreferences();
			_backgroundController = new LoadingScreenBackgroundController(Logger);
			_preferencesWatcher = new PreferencesFileWatcher(Logger);
			RecreateImageWatcher();
			HarmonyPatches.SetModInstance(this);
			RuntimeReflection.Initialize();
			LoadSpritesOnce();
			Logger.Msg("Monitoring custom loading images under '" + _preferences.ResolvedBaseFolder + "'.");
		}

		private void LoadSpritesOnce()
		{
			_backgroundController?.MarkSpriteCacheDirty();
			if (_preferences != null)
			{
				_backgroundController?.LoadSpritesOnce(_preferences);
			}
		}

		public override void OnUpdate()
		{
			PreferencesFileWatcher? preferencesWatcher = _preferencesWatcher;
			if (preferencesWatcher != null && preferencesWatcher.TryConsumeScheduledReload())
			{
				ReloadPreferencesFromDisk();
			}
			ImageFolderWatcher? imageFolderWatcher = _imageFolderWatcher;
			if (imageFolderWatcher != null && imageFolderWatcher.TryConsumeScheduledReload())
			{
				_backgroundController?.MarkSpriteCacheDirty();
			}
		}

		public override void OnApplicationQuit()
		{
			_imageFolderWatcher?.Dispose();
			_imageFolderWatcher = null;
			_preferencesWatcher?.Dispose();
			_preferencesWatcher = null;
			_backgroundController?.Dispose();
			_backgroundController = null;
			_preferences = null;
			Instance = null;
		}

		internal void ApplyLoadingScreenOverrides(object loadingScreen)
		{
			if (_preferences != null && _backgroundController != null)
			{
				_backgroundController.ApplyToLoadingScreen(loadingScreen, _preferences);
			}
		}

		private void ReloadPreferencesFromDisk()
		{
			string text = _preferencesWatcher?.FilePath;
			if (string.IsNullOrWhiteSpace(text))
			{
				return;
			}
			if (!PreferencesFileParser.TryReadSnapshot(text, out PreferencesSnapshot snapshot))
			{
				_preferencesWatcher?.ScheduleReload();
				return;
			}
			ModPreferences? preferences = _preferences;
			if (preferences != null && preferences.ApplySnapshot(snapshot))
			{
				_backgroundController?.MarkSpriteCacheDirty();
				RecreateImageWatcher();
				Logger.Msg("Reloaded preferences from MelonPreferences.cfg. Using image folder '" + _preferences.ResolvedBaseFolder + "'.");
			}
		}

		private void RecreateImageWatcher()
		{
			_imageFolderWatcher?.Dispose();
			_imageFolderWatcher = null;
			ModPreferences? preferences = _preferences;
			if (preferences != null && preferences.WatchFolders)
			{
				_imageFolderWatcher = new ImageFolderWatcher(_preferences.ResolvedBaseFolder, Logger);
			}
		}
	}
}
namespace CustomLoadingScreens.Utils
{
	public static class Constants
	{
		public static class PreferenceKeys
		{
			public const string ENABLED = "enabled";

			public const string BASE_FOLDER = "baseFolder";

			public const string USE_NORMAL_IMAGES_FOR_TUTORIAL = "useNormalImagesForTutorial";

			public const string WATCH_FOLDERS = "watchFolders";

			public const string LOG_MISSING_IMAGES_ONCE = "logMissingImagesOnce";
		}

		public static class Defaults
		{
			public const bool ENABLED = true;

			public const bool USE_NORMAL_IMAGES_FOR_TUTORIAL = true;

			public const bool WATCH_FOLDERS = true;

			public const bool LOG_MISSING_IMAGES_ONCE = true;

			public const string BACKGROUND_IMAGES_FOLDER_NAME = "BackgroundImages";

			public const string TUTORIAL_BACKGROUND_IMAGES_FOLDER_NAME = "TutorialBackgroundImages";
		}

		public static class Game
		{
			public const string GAME_STUDIO = "TVGS";

			public const string GAME_NAME = "Schedule I";
		}

		public const string MOD_NAME = "Custom Loading Screens";

		public const string MOD_FOLDER_NAME = "CustomLoadingScreens";

		public const string MOD_VERSION = "1.1.0";

		public const string MOD_AUTHOR = "Riccaforte";

		public const string PREFERENCES_CATEGORY = "Custom Loading Screens";
	}
	internal static class RuntimeReflection
	{
		private static Func<Texture2D, byte[], bool>? _loadImageInvoker;

		public static void Initialize()
		{
			if (_loadImageInvoker != null)
			{
				return;
			}
			Type[] array = FindLoadedTypes("UnityEngine.ImageConversion").ToArray();
			if (array.Length == 0)
			{
				throw new InvalidOperationException("UnityEngine.ImageConversion type was not found in the loaded runtime assemblies.");
			}
			bool flag = IsIl2CppRuntime();
			MethodInfo methodInfo = (flag ? FindRuntimeLoadImageMethod(array, IsIl2CppByteArrayType) : FindRuntimeLoadImageMethod(array, (Type parameterType) => parameterType == typeof(byte[])));
			if (methodInfo == null)
			{
				methodInfo = (flag ? FindRuntimeLoadImageMethod(array, (Type parameterType) => parameterType == typeof(byte[])) : FindRuntimeLoadImageMethod(array, IsIl2CppByteArrayType));
			}
			if (methodInfo == null)
			{
				throw new InvalidOperationException("UnityEngine.ImageConversion.LoadImage did not expose a supported overload across the loaded runtime assemblies. Assemblies searched: " + string.Join(", ", array.Select((Type type) => type.Assembly.GetName().Name).Distinct<string>(StringComparer.Ordinal)) + ".");
			}
			bool flag2 = methodInfo != null && methodInfo.GetParameters().Length >= 2 && IsIl2CppByteArrayType(methodInfo.GetParameters()[1].ParameterType);
			_loadImageInvoker = ((flag && flag2) ? CreateIl2CppByteArrayInvoker(methodInfo) : CreateManagedByteArrayInvoker(methodInfo));
		}

		public static bool TryLoadImage(Texture2D texture, byte[] imageBytes)
		{
			return _loadImageInvoker != null && _loadImageInvoker(texture, imageBytes);
		}

		private static bool IsIl2CppByteArrayType(Type parameterType)
		{
			return parameterType.IsGenericType && string.Equals(parameterType.GetGenericTypeDefinition().FullName, "Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray`1", StringComparison.Ordinal) && parameterType.GenericTypeArguments.Length == 1 && parameterType.GenericTypeArguments[0] == typeof(byte);
		}

		private static Func<Texture2D, byte[], bool> CreateManagedByteArrayInvoker(MethodInfo managedOverload)
		{
			ParameterInfo[] parameters = managedOverload.GetParameters();
			return delegate(Texture2D texture, byte[] imageBytes)
			{
				object obj = ((parameters.Length == 2) ? managedOverload.Invoke(null, new object[2] { texture, imageBytes }) : managedOverload.Invoke(null, new object[3] { texture, imageBytes, false }));
				return obj != null && Convert.ToBoolean(obj);
			};
		}

		private static Func<Texture2D, byte[], bool> CreateIl2CppByteArrayInvoker(MethodInfo il2cppOverload)
		{
			ParameterInfo[] parameters = il2cppOverload.GetParameters();
			return delegate(Texture2D texture, byte[] imageBytes)
			{
				Type type = FindIl2CppByteArrayType();
				if (type == null)
				{
					return false;
				}
				ConstructorInfo constructor = type.GetConstructor(new Type[1] { typeof(byte[]) });
				if (constructor == null)
				{
					return false;
				}
				object obj = constructor.Invoke(new object[1] { imageBytes });
				object obj2 = ((parameters.Length != 2) ? il2cppOverload.Invoke(null, new object[3] { texture, obj, false }) : il2cppOverload.Invoke(null, new object[2] { texture, obj }));
				return obj2 != null && Convert.ToBoolean(obj2);
			};
		}

		private static Type? FindIl2CppByteArrayType()
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				if (!string.Equals(assembly.GetName().Name, "Il2CppInterop.Runtime", StringComparison.Ordinal))
				{
					continue;
				}
				try
				{
					Type type = assembly.GetType("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray`1");
					if (type != null)
					{
						try
						{
							return type.MakeGenericType(typeof(byte));
						}
						catch
						{
						}
					}
				}
				catch
				{
				}
				foreach (Type loadableType in GetLoadableTypes(assembly))
				{
					if (string.Equals(loadableType.FullName, "Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppStructArray`1", StringComparison.Ordinal) && loadableType.GenericTypeArguments.Length == 1 && loadableType.GenericTypeArguments[0] == typeof(byte))
					{
						return loadableType;
					}
				}
			}
			return null;
		}

		private static MethodInfo? FindRuntimeLoadImageMethod(IEnumerable<Type> imageConversionTypes, Func<Type, bool> matchesSecondParameterType)
		{
			foreach (Type imageConversionType in imageConversionTypes)
			{
				MethodInfo[] methods = imageConversionType.GetMethods(BindingFlags.Static | BindingFlags.Public);
				foreach (MethodInfo methodInfo in methods)
				{
					if (string.Equals(methodInfo.Name, "LoadImage", StringComparison.Ordinal) && !(methodInfo.ReturnType != typeof(bool)))
					{
						ParameterInfo[] parameters = methodInfo.GetParameters();
						if (parameters.Length >= 2 && parameters.Length <= 3 && !(parameters[0].ParameterType != typeof(Texture2D)) && matchesSecondParameterType(parameters[1].ParameterType) && (parameters.Length == 2 || parameters[2].ParameterType == typeof(bool)))
						{
							return methodInfo;
						}
					}
				}
			}
			return null;
		}

		private static bool IsIl2CppRuntime()
		{
			return AppDomain.CurrentDomain.GetAssemblies().Any((Assembly assembly) => string.Equals(assembly.GetName().Name, "Il2CppInterop.Runtime", StringComparison.Ordinal));
		}

		private static IEnumerable<Type> FindLoadedTypes(string fullTypeName)
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				Type exactType = assembly.GetType(fullTypeName, throwOnError: false, ignoreCase: false);
				if (exactType != null)
				{
					yield return exactType;
					continue;
				}
				foreach (Type candidateType in GetLoadableTypes(assembly))
				{
					if (string.Equals(candidateType.FullName, fullTypeName, StringComparison.Ordinal))
					{
						yield return candidateType;
						break;
					}
				}
			}
		}

		private static IEnumerable<Type> GetLoadableTypes(Assembly assembly)
		{
			try
			{
				return assembly.GetTypes();
			}
			catch (ReflectionTypeLoadException ex)
			{
				return ex.Types.OfType<Type>();
			}
		}
	}
	internal static class SpriteLoader
	{
		private static readonly Log _logger = new Log("SpriteLoader");

		public static Sprite? TryCreateSprite(string filePath)
		{
			try
			{
				byte[] imageBytes = File.ReadAllBytes(filePath);
				Texture2D val = CreateTexture(imageBytes, Path.GetFileNameWithoutExtension(filePath));
				if (!RuntimeReflection.TryLoadImage(val, imageBytes))
				{
					Object.Destroy((Object)(object)val);
					_logger.Warning("Skipping custom loading image '" + filePath + "' because Unity could not decode it.");
					return null;
				}
				return CreateSpriteFromTexture(val);
			}
			catch (IOException ex)
			{
				_logger.Warning("Skipping custom loading image '" + filePath + "' because it could not be read: " + ex.Message);
				return null;
			}
			catch (UnauthorizedAccessException ex2)
			{
				_logger.Warning("Skipping custom loading image '" + filePath + "' because it could not be read: " + ex2.Message);
				return null;
			}
			catch (Exception ex3)
			{
				_logger.Warning("Skipping custom loading image '" + filePath + "' because it failed to load: " + ex3.Message);
				return null;
			}
		}

		private static Texture2D CreateTexture(byte[] imageBytes, string name)
		{
			//IL_0005: Unknown result type (might be due to invalid IL or missing references)
			//IL_000b: Expected O, but got Unknown
			Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false);
			((Object)val).name = name;
			((Texture)val).wrapMode = (TextureWrapMode)1;
			((Texture)val).filterMode = (FilterMode)1;
			return val;
		}

		private static Sprite CreateSpriteFromTexture(Texture2D texture)
		{
			//IL_001a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0029: Unknown result type (might be due to invalid IL or missing references)
			Sprite val = Sprite.Create(texture, new Rect(0f, 0f, (float)((Texture)texture).width, (float)((Texture)texture).height), new Vector2(0.5f, 0.5f), 100f);
			((Object)val).name = ((Object)texture).name;
			return val;
		}

		public static Sprite[] LoadSprites(string[] filePaths)
		{
			if (filePaths.Length == 0)
			{
				return Array.Empty<Sprite>();
			}
			List<Sprite> list = new List<Sprite>(filePaths.Length);
			foreach (string filePath in filePaths)
			{
				Sprite val = TryCreateSprite(filePath);
				if ((Object)(object)val != (Object)null)
				{
					list.Add(val);
				}
			}
			return list.ToArray();
		}
	}
}
namespace CustomLoadingScreens.LoadingScreen
{
	internal static class ImageCatalog
	{
		private static readonly string[] SupportedExtensions = new string[3] { ".png", ".jpg", ".jpeg" };

		public static string GetBackgroundFolderPath(string baseFolder)
		{
			return Path.Combine(baseFolder, "BackgroundImages");
		}

		public static string GetTutorialBackgroundFolderPath(string baseFolder)
		{
			return Path.Combine(baseFolder, "TutorialBackgroundImages");
		}

		public static string[] GetImageFiles(string folderPath)
		{
			if (!Directory.Exists(folderPath))
			{
				return Array.Empty<string>();
			}
			return Directory.GetFiles(folderPath).Where(IsSupportedImageFile).OrderBy<string, string>(Path.GetFileName, StringComparer.OrdinalIgnoreCase)
				.ToArray();
		}

		public static bool IsSupportedImageFile(string path)
		{
			string extension = Path.GetExtension(path);
			return SupportedExtensions.Contains<string>(extension, StringComparer.OrdinalIgnoreCase);
		}
	}
	internal sealed class LoadingScreenBackgroundController : IDisposable
	{
		private readonly Log _logger;

		private readonly SpriteCache _spriteCache;

		private object? _capturedLoadingScreenInstance;

		private object? _originalBackgroundImages;

		private object? _originalTutorialBackgroundImages;

		public LoadingScreenBackgroundController(Log logger)
		{
			_logger = logger;
			_spriteCache = new SpriteCache(logger);
		}

		public void MarkSpriteCacheDirty()
		{
			_spriteCache.MarkDirty();
		}

		public void LoadSpritesOnce(ModPreferences preferences)
		{
			_spriteCache.EnsureLoaded(preferences);
		}

		public void ApplyToLoadingScreen(object loadingScreen, ModPreferences preferences)
		{
			CaptureOriginalArrays(loadingScreen);
			if (!preferences.IsEnabled)
			{
				RestoreOriginalArrays(loadingScreen);
				return;
			}
			_spriteCache.EnsureLoaded(preferences);
			ApplySpritesToLoadingScreen(loadingScreen, "BackgroundImages", _spriteCache.NormalSprites, _originalBackgroundImages);
			ApplySpritesToLoadingScreen(loadingScreen, "TutorialBackgroundImages", _spriteCache.TutorialSprites, _originalTutorialBackgroundImages);
		}

		public void Dispose()
		{
			_spriteCache.Dispose();
		}

		private void CaptureOriginalArrays(object loadingScreen)
		{
			if (_capturedLoadingScreenInstance != loadingScreen)
			{
				_capturedLoadingScreenInstance = loadingScreen;
				_originalBackgroundImages = RuntimeInterop.GetMemberValue(loadingScreen, "BackgroundImages");
				_originalTutorialBackgroundImages = RuntimeInterop.GetMemberValue(loadingScreen, "TutorialBackgroundImages");
				if (_originalBackgroundImages == null || _originalTutorialBackgroundImages == null)
				{
					_logger.Warning("Could not capture one or more original loading-screen image arrays. Fallback restoration may be limited.");
				}
			}
		}

		private static void ApplySpritesToLoadingScreen(object loadingScreen, string memberName, Sprite[] customSprites, object? originalValue)
		{
			if (customSprites.Length != 0)
			{
				try
				{
					RuntimeInterop.SetSpriteArrayMemberValue(loadingScreen, memberName, customSprites);
					return;
				}
				catch (InvalidOperationException)
				{
					return;
				}
			}
			if (originalValue != null)
			{
				RuntimeInterop.SetMemberValue(loadingScreen, memberName, originalValue);
			}
		}

		private void RestoreOriginalArrays(object loadingScreen)
		{
			if (_originalBackgroundImages != null)
			{
				RuntimeInterop.SetMemberValue(loadingScreen, "BackgroundImages", _originalBackgroundImages);
			}
			if (_originalTutorialBackgroundImages != null)
			{
				RuntimeInterop.SetMemberValue(loadingScreen, "TutorialBackgroundImages", _originalTutorialBackgroundImages);
			}
		}
	}
	internal sealed class SpriteCache : IDisposable
	{
		private readonly Log _logger;

		private bool _isDirty = true;

		private string? _lastBaseFolder;

		private bool _lastUseNormalImagesForTutorial;

		public Sprite[] NormalSprites { get; private set; } = Array.Empty<Sprite>();

		public Sprite[] TutorialSprites { get; private set; } = Array.Empty<Sprite>();

		public SpriteCache(Log logger)
		{
			_logger = logger;
		}

		public void MarkDirty()
		{
			_isDirty = true;
		}

		public void EnsureLoaded(ModPreferences preferences)
		{
			string resolvedBaseFolder = preferences.ResolvedBaseFolder;
			if (_isDirty || !string.Equals(_lastBaseFolder, resolvedBaseFolder, StringComparison.Ordinal) || _lastUseNormalImagesForTutorial != preferences.UseNormalImagesForTutorial)
			{
				Rebuild(preferences, resolvedBaseFolder);
			}
		}

		public void Dispose()
		{
			ClearOwnedObjects();
		}

		private void Rebuild(ModPreferences preferences, string baseFolder)
		{
			ClearOwnedObjects();
			_lastBaseFolder = baseFolder;
			_lastUseNormalImagesForTutorial = preferences.UseNormalImagesForTutorial;
			_isDirty = false;
			string backgroundFolderPath = ImageCatalog.GetBackgroundFolderPath(baseFolder);
			string tutorialBackgroundFolderPath = ImageCatalog.GetTutorialBackgroundFolderPath(baseFolder);
			Directory.CreateDirectory(backgroundFolderPath);
			Directory.CreateDirectory(tutorialBackgroundFolderPath);
			NormalSprites = SpriteLoader.LoadSprites(ImageCatalog.GetImageFiles(backgroundFolderPath));
			Sprite[] array = SpriteLoader.LoadSprites(ImageCatalog.GetImageFiles(tutorialBackgroundFolderPath));
			TutorialSprites = (Sprite[])((array.Length != 0) ? array : (preferences.UseNormalImagesForTutorial ? ((Array)NormalSprites) : ((Array)Array.Empty<Sprite>())));
			if (NormalSprites.Length == 0 && TutorialSprites.Length == 0)
			{
				_logger.Msg("No custom loading images found. Add images to '" + backgroundFolderPath + "' and '" + tutorialBackgroundFolderPath + "'.");
			}
			else
			{
				_logger.Msg($"Loaded {NormalSprites.Length} normal loading images and {array.Length} tutorial loading images.");
			}
		}

		private void ClearOwnedObjects()
		{
			Sprite[] normalSprites = NormalSprites;
			foreach (Sprite val in normalSprites)
			{
				if ((Object)(object)val != (Object)null)
				{
					Object.Destroy((Object)(object)val);
				}
			}
			NormalSprites = Array.Empty<Sprite>();
			TutorialSprites = Array.Empty<Sprite>();
		}
	}
}
namespace CustomLoadingScreens.Integrations
{
	public static class HarmonyPatches
	{
		[HarmonyPatch]
		private static class LoadingScreenOpenPatch
		{
			[HarmonyPrepare]
			private static bool Prepare()
			{
				return CanPatch(GameTypeNames.LoadingScreen, "Open", typeof(bool));
			}

			[HarmonyTargetMethod]
			private static MethodBase TargetMethod()
			{
				return GetRequiredPatchMethod(GameTypeNames.LoadingScreen, "Open", typeof(bool));
			}

			[HarmonyPrefix]
			private static void Prefix(object __instance, bool __0)
			{
				if (_modInstance == null)
				{
					return;
				}
				try
				{
					_modInstance.ApplyLoadingScreenOverrides(__instance);
				}
				catch (Exception arg)
				{
					_modInstance.Logger.Error($"Failed to apply custom loading screen images (tutorial={__0}): {arg}");
				}
			}
		}

		private static Core? _modInstance;

		public static void SetModInstance(Core modInstance)
		{
			_modInstance = modInstance;
		}

		private static bool CanPatch(string[] typeNames, string methodName, params Type[] argumentTypes)
		{
			Type type = RuntimeInterop.ResolveGameType(typeNames);
			return type != null && AccessTools.Method(type, methodName, argumentTypes, (Type[])null) != null;
		}

		private static MethodBase GetRequiredPatchMethod(string[] typeNames, string methodName, params Type[] argumentTypes)
		{
			Type type = RuntimeInterop.ResolveGameType(typeNames);
			MethodBase methodBase = ((type == null) ? null : AccessTools.Method(type, methodName, argumentTypes, (Type[])null));
			if (methodBase != null)
			{
				return methodBase;
			}
			throw new MissingMethodException("Could not resolve Harmony patch target '" + methodName + "'.");
		}
	}
}
namespace CustomLoadingScreens.Integrations.Interop
{
	internal static class GameTypeNames
	{
		public static readonly string[] LoadingScreen = new string[2] { "ScheduleOne.UI.LoadingScreen", "Il2CppScheduleOne.UI.LoadingScreen" };
	}
	internal static class RuntimeInterop
	{
		private const BindingFlags MemberLookupFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic;

		private static readonly ConcurrentDictionary<(Type Type, string MemberName), MemberInfo?> MemberCache = new ConcurrentDictionary<(Type, string), MemberInfo>();

		private static readonly ConcurrentDictionary<string, Type?> TypeCache = new ConcurrentDictionary<string, Type>(StringComparer.Ordinal);

		public static IReadOnlyList<object?> MaterializeObjects(object? value)
		{
			if (value == null)
			{
				return Array.Empty<object>();
			}
			if (value is IEnumerable enumerable)
			{
				List<object> list = new List<object>();
				foreach (object item in enumerable)
				{
					list.Add(item);
				}
				return list;
			}
			object memberValue = GetMemberValue(value, "Count");
			if (memberValue == null)
			{
				return Array.Empty<object>();
			}
			int num = Convert.ToInt32(memberValue);
			PropertyInfo property = value.GetType().GetProperty("Item", new Type[1] { typeof(int) });
			if (property == null)
			{
				return Array.Empty<object>();
			}
			List<object> list2 = new List<object>(num);
			for (int i = 0; i < num; i++)
			{
				list2.Add(property.GetValue(value, new object[1] { i }));
			}
			return list2;
		}

		public static object? GetMemberValue(object? instance, string memberName)
		{
			if (instance == null)
			{
				return null;
			}
			MemberInfo memberInfo = ResolveMember(instance.GetType(), memberName);
			if (memberInfo is PropertyInfo propertyInfo)
			{
				return propertyInfo.GetValue(instance);
			}
			return (memberInfo is FieldInfo fieldInfo) ? fieldInfo.GetValue(instance) : null;
		}

		public static void SetMemberValue(object instance, string memberName, object? value)
		{
			MemberInfo memberInfo = ResolveMember(instance.GetType(), memberName);
			if (memberInfo is PropertyInfo { CanWrite: not false } propertyInfo)
			{
				propertyInfo.SetValue(instance, value);
			}
			else if (memberInfo is FieldInfo fieldInfo)
			{
				fieldInfo.SetValue(instance, value);
			}
		}

		public static void SetSpriteArrayMemberValue(object instance, string memberName, Sprite[] sprites)
		{
			MemberInfo member = ResolveMember(instance.GetType(), memberName);
			Type memberType = GetMemberType(member);
			if (!(memberType == null))
			{
				object value = CreateCompatibleSpriteArrayValue(memberType, sprites);
				SetMemberValue(instance, memberName, value);
			}
		}

		public static Type? ResolveGameType(IEnumerable<string> typeNames)
		{
			foreach (string typeName in typeNames)
			{
				Type type = ResolveLoadedType(typeName);
				if (type != null)
				{
					return type;
				}
			}
			return null;
		}

		private static MemberInfo? ResolveMember(Type type, string memberName)
		{
			return MemberCache.GetOrAdd((type, memberName), ((Type Type, string MemberName) key) => FindMember(key.Type, key.MemberName));
		}

		private static MemberInfo? FindMember(Type type, string memberName)
		{
			Type type2 = type;
			while (type2 != null)
			{
				PropertyInfo property = type2.GetProperty(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (property != null)
				{
					return property;
				}
				FieldInfo field = type2.GetField(memberName, BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
				if (field != null)
				{
					return field;
				}
				type2 = type2.BaseType;
			}
			return null;
		}

		private static Type? GetMemberType(MemberInfo? member)
		{
			if (member is PropertyInfo propertyInfo)
			{
				return propertyInfo.PropertyType;
			}
			return (member is FieldInfo fieldInfo) ? fieldInfo.FieldType : null;
		}

		private static object CreateCompatibleSpriteArrayValue(Type memberType, Sprite[] sprites)
		{
			if (memberType == typeof(Sprite[]))
			{
				return sprites;
			}
			if (memberType.IsArray && memberType.GetElementType() == typeof(Sprite))
			{
				return sprites;
			}
			string? fullName = memberType.FullName;
			if (fullName != null && fullName.StartsWith("Il2CppInterop.Runtime.InteropTypes.Arrays.Il2CppReferenceArray`1", StringComparison.Ordinal))
			{
				ConstructorInfo constructor = memberType.GetConstructor(new Type[1] { typeof(Sprite[]) });
				if (constructor != null)
				{
					return constructor.Invoke(new object[1] { sprites });
				}
			}
			throw new InvalidOperationException("Unsupported sprite array target type '" + memberType.FullName + "'.");
		}

		private static Type? ResolveLoadedType(string typeName)
		{
			return TypeCache.GetOrAdd(typeName, (string name) => FindLoadedType(name));
		}

		private static Type? FindLoadedType(string typeName)
		{
			Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies();
			foreach (Assembly assembly in assemblies)
			{
				Type type = assembly.GetType(typeName, throwOnError: false, ignoreCase: false);
				if (type != null)
				{
					return type;
				}
			}
			return null;
		}
	}
}
namespace CustomLoadingScreens.Config
{
	internal sealed class ImageFolderWatcher : IDisposable
	{
		private static readonly TimeSpan ImageReloadDebounce = TimeSpan.FromMilliseconds(250.0);

		private readonly object _reloadLock = new object();

		private readonly string _baseFolderPath;

		private readonly string _normalizedBaseFolderPath;

		private FileSystemWatcher? _watcher;

		private DateTime? _reloadDueUtc;

		public ImageFolderWatcher(string baseFolderPath, Log logger)
		{
			_baseFolderPath = baseFolderPath;
			_normalizedBaseFolderPath = NormalizePath(baseFolderPath);
			Initialize(logger);
		}

		public void ScheduleReload()
		{
			lock (_reloadLock)
			{
				_reloadDueUtc = DateTime.UtcNow + ImageReloadDebounce;
			}
		}

		public bool TryConsumeScheduledReload()
		{
			lock (_reloadLock)
			{
				DateTime? reloadDueUtc = _reloadDueUtc;
				if (!reloadDueUtc.HasValue || DateTime.UtcNow < _reloadDueUtc.Value)
				{
					return false;
				}
				_reloadDueUtc = null;
				return true;
			}
		}

		public void Dispose()
		{
			if (_watcher != null)
			{
				_watcher.EnableRaisingEvents = false;
				_watcher.Changed -= OnFolderChanged;
				_watcher.Created -= OnFolderChanged;
				_watcher.Deleted -= OnFolderChanged;
				_watcher.Renamed -= OnFolderRenamed;
				_watcher.Dispose();
				_watcher = null;
			}
		}

		private void Initialize(Log logger)
		{
			string text = (Directory.Exists(_baseFolderPath) ? _baseFolderPath : Path.GetDirectoryName(_baseFolderPath));
			if (string.IsNullOrWhiteSpace(text) || !Directory.Exists(text))
			{
				logger.Warning("Custom image directory watcher could not start because '" + text + "' does not exist.");
				return;
			}
			_watcher = new FileSystemWatcher(text)
			{
				Filter = "*.*",
				NotifyFilter = (NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime),
				IncludeSubdirectories = true,
				EnableRaisingEvents = true
			};
			_watcher.Changed += OnFolderChanged;
			_watcher.Created += OnFolderChanged;
			_watcher.Deleted += OnFolderChanged;
			_watcher.Renamed += OnFolderRenamed;
		}

		private void OnFolderChanged(object sender, FileSystemEventArgs e)
		{
			if (ShouldWatchPath(e.FullPath))
			{
				ScheduleReload();
			}
		}

		private void OnFolderRenamed(object sender, RenamedEventArgs e)
		{
			if (ShouldWatchPath(e.FullPath) || ShouldWatchPath(e.OldFullPath))
			{
				ScheduleReload();
			}
		}

		private bool ShouldWatchPath(string path)
		{
			string text = NormalizePath(path);
			if (text.StartsWith(_normalizedBaseFolderPath, StringComparison.OrdinalIgnoreCase))
			{
				return true;
			}
			return ImageCatalog.IsSupportedImageFile(path);
		}

		private static string NormalizePath(string path)
		{
			return Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar);
		}
	}
	internal sealed class ModPreferences
	{
		private readonly MelonPreferences_Entry<bool> _enabledPreference;

		private readonly MelonPreferences_Entry<string> _baseFolderPreference;

		private readonly MelonPreferences_Entry<bool> _useNormalImagesForTutorialPreference;

		private readonly MelonPreferences_Entry<bool> _watchFoldersPreference;

		private readonly MelonPreferences_Entry<bool> _logMissingImagesOncePreference;

		public bool IsEnabled => _enabledPreference.Value;

		public string BaseFolder => _baseFolderPreference.Value?.Trim() ?? string.Empty;

		public bool UseNormalImagesForTutorial => _useNormalImagesForTutorialPreference.Value;

		public bool WatchFolders => _watchFoldersPreference.Value;

		public bool LogMissingImagesOnce => _logMissingImagesOncePreference.Value;

		public string ResolvedBaseFolder
		{
			get
			{
				if (!string.IsNullOrWhiteSpace(BaseFolder))
				{
					return BaseFolder;
				}
				string userDataDirectory = MelonEnvironment.UserDataDirectory;
				if (!string.IsNullOrWhiteSpace(userDataDirectory))
				{
					return Path.Combine(userDataDirectory, "CustomLoadingScreens");
				}
				return Path.Combine(AppContext.BaseDirectory, "CustomLoadingScreens");
			}
		}

		public ModPreferences()
		{
			MelonPreferences_Category val = MelonPreferences.CreateCategory("Custom Loading Screens");
			_enabledPreference = val.CreateEntry<bool>("enabled", true, "Whether the mod is enabled.", (string)null, false, false, (ValueValidator)null, (string)null);
			_baseFolderPreference = val.CreateEntry<string>("baseFolder", string.Empty, "Optional override for the custom loading image base folder.", (string)null, false, false, (ValueValidator)null, (string)null);
			_useNormalImagesForTutorialPreference = val.CreateEntry<bool>("useNormalImagesForTutorial", true, "Whether tutorial loading screens should reuse normal images when no tutorial images are present.", (string)null, false, false, (ValueValidator)null, (string)null);
			_watchFoldersPreference = val.CreateEntry<bool>("watchFolders", true, "Whether the mod should watch the custom image folder for changes.", (string)null, false, false, (ValueValidator)null, (string)null);
			_logMissingImagesOncePreference = val.CreateEntry<bool>("logMissingImagesOnce", true, "Whether to log missing image folders or images only once until valid images are found.", (string)null, false, false, (ValueValidator)null, (string)null);
		}

		public bool ApplySnapshot(PreferencesSnapshot snapshot)
		{
			bool result = false;
			bool? isEnabled = snapshot.IsEnabled;
			if (isEnabled.HasValue)
			{
				bool valueOrDefault = isEnabled == true;
				if (_enabledPreference.Value != valueOrDefault)
				{
					_enabledPreference.Value = valueOrDefault;
					result = true;
				}
			}
			if (snapshot.BaseFolder != null && !string.Equals(_baseFolderPreference.Value, snapshot.BaseFolder, StringComparison.Ordinal))
			{
				_baseFolderPreference.Value = snapshot.BaseFolder;
				result = true;
			}
			isEnabled = snapshot.UseNormalImagesForTutorial;
			if (isEnabled.HasValue)
			{
				bool valueOrDefault2 = isEnabled == true;
				if (_useNormalImagesForTutorialPreference.Value != valueOrDefault2)
				{
					_useNormalImagesForTutorialPreference.Value = valueOrDefault2;
					result = true;
				}
			}
			isEnabled = snapshot.WatchFolders;
			if (isEnabled.HasValue)
			{
				bool valueOrDefault3 = isEnabled == true;
				if (_watchFoldersPreference.Value != valueOrDefault3)
				{
					_watchFoldersPreference.Value = valueOrDefault3;
					result = true;
				}
			}
			isEnabled = snapshot.LogMissingImagesOnce;
			if (isEnabled.HasValue)
			{
				bool valueOrDefault4 = isEnabled == true;
				if (_logMissingImagesOncePreference.Value != valueOrDefault4)
				{
					_logMissingImagesOncePreference.Value = valueOrDefault4;
					result = true;
				}
			}
			return result;
		}
	}
	internal static class PreferencesFileParser
	{
		public static bool TryReadSnapshot(string preferencesFilePath, out PreferencesSnapshot snapshot)
		{
			snapshot = new PreferencesSnapshot();
			if (!File.Exists(preferencesFilePath))
			{
				return false;
			}
			try
			{
				string[] array = File.ReadAllLines(preferencesFilePath);
				bool flag = false;
				bool? isEnabled = null;
				string baseFolder = null;
				bool? useNormalImagesForTutorial = null;
				bool? watchFolders = null;
				bool? logMissingImagesOnce = null;
				string[] array2 = array;
				foreach (string text in array2)
				{
					string text2 = text.Trim();
					if (text2.Length == 0)
					{
						continue;
					}
					if (text2.StartsWith("[", StringComparison.Ordinal) && text2.EndsWith("]", StringComparison.Ordinal))
					{
						string a = text2.Substring(1, text2.Length - 2);
						flag = string.Equals(a, "Custom Loading Screens", StringComparison.Ordinal);
					}
					else
					{
						if (!flag)
						{
							continue;
						}
						int num = text2.IndexOf('=');
						if (num > 0)
						{
							string a2 = text2.Substring(0, num).Trim();
							string value = text2.Substring(num + 1).Trim();
							bool result2;
							bool result3;
							bool result4;
							if (string.Equals(a2, "enabled", StringComparison.Ordinal) && bool.TryParse(value, out var result))
							{
								isEnabled = result;
							}
							else if (string.Equals(a2, "baseFolder", StringComparison.Ordinal))
							{
								baseFolder = TrimEnclosingQuotes(value);
							}
							else if (string.Equals(a2, "useNormalImagesForTutorial", StringComparison.Ordinal) && bool.TryParse(value, out result2))
							{
								useNormalImagesForTutorial = result2;
							}
							else if (string.Equals(a2, "watchFolders", StringComparison.Ordinal) && bool.TryParse(value, out result3))
							{
								watchFolders = result3;
							}
							else if (string.Equals(a2, "logMissingImagesOnce", StringComparison.Ordinal) && bool.TryParse(value, out result4))
							{
								logMissingImagesOnce = result4;
							}
						}
					}
				}
				snapshot = new PreferencesSnapshot(isEnabled, baseFolder, useNormalImagesForTutorial, watchFolders, logMissingImagesOnce);
				return true;
			}
			catch (IOException)
			{
				return false;
			}
			catch (UnauthorizedAccessException)
			{
				return false;
			}
		}

		private static string TrimEnclosingQuotes(string value)
		{
			if (value.Length >= 2 && value[0] == '"')
			{
				if (value[value.Length - 1] == '"')
				{
					return value.Substring(1, value.Length - 2);
				}
			}
			return value;
		}
	}
	internal sealed class PreferencesFileWatcher : IDisposable
	{
		private const string PreferencesFileName = "MelonPreferences.cfg";

		private static readonly TimeSpan PreferencesReloadDebounce = TimeSpan.FromMilliseconds(150.0);

		private readonly object _preferencesReloadLock = new object();

		private FileSystemWatcher? _preferencesWatcher;

		private DateTime? _preferencesReloadDueUtc;

		public string? FilePath { get; }

		public PreferencesFileWatcher(Log logger)
		{
			FilePath = ResolvePreferencesFilePath();
			Initialize(logger);
		}

		public void ScheduleReload()
		{
			lock (_preferencesReloadLock)
			{
				_preferencesReloadDueUtc = DateTime.UtcNow + PreferencesReloadDebounce;
			}
		}

		public bool TryConsumeScheduledReload()
		{
			lock (_preferencesReloadLock)
			{
				DateTime? preferencesReloadDueUtc = _preferencesReloadDueUtc;
				if (!preferencesReloadDueUtc.HasValue || DateTime.UtcNow < _preferencesReloadDueUtc.Value)
				{
					return false;
				}
				_preferencesReloadDueUtc = null;
				return true;
			}
		}

		public void Dispose()
		{
			if (_preferencesWatcher != null)
			{
				_preferencesWatcher.EnableRaisingEvents = false;
				_preferencesWatcher.Changed -= OnPreferencesFileChanged;
				_preferencesWatcher.Created -= OnPreferencesFileChanged;
				_preferencesWatcher.Renamed -= OnPreferencesFileRenamed;
				_preferencesWatcher.Dispose();
				_preferencesWatcher = null;
			}
		}

		private void Initialize(Log logger)
		{
			if (string.IsNullOrWhiteSpace(FilePath))
			{
				logger.Warning("Could not resolve the MelonPreferences path; live config reload is disabled.");
				return;
			}
			string directoryName = Path.GetDirectoryName(FilePath);
			if (string.IsNullOrWhiteSpace(directoryName) || !Directory.Exists(directoryName))
			{
				logger.Warning("MelonPreferences directory was not found: " + directoryName);
				return;
			}
			_preferencesWatcher = new FileSystemWatcher(directoryName, "MelonPreferences.cfg")
			{
				NotifyFilter = (NotifyFilters.FileName | NotifyFilters.Size | NotifyFilters.LastWrite | NotifyFilters.CreationTime),
				IncludeSubdirectories = false,
				EnableRaisingEvents = true
			};
			_preferencesWatcher.Changed += OnPreferencesFileChanged;
			_preferencesWatcher.Created += OnPreferencesFileChanged;
			_preferencesWatcher.Renamed += OnPreferencesFileRenamed;
		}

		private void OnPreferencesFileChanged(object sender, FileSystemEventArgs e)
		{
			ScheduleReload();
		}

		private void OnPreferencesFileRenamed(object sender, RenamedEventArgs e)
		{
			ScheduleReload();
		}

		private static string? ResolvePreferencesFilePath()
		{
			string userDataDirectory = MelonEnvironment.UserDataDirectory;
			if (string.IsNullOrWhiteSpace(userDataDirectory))
			{
				return null;
			}
			return Path.Combine(userDataDirectory, "MelonPreferences.cfg");
		}
	}
	internal sealed class PreferencesSnapshot
	{
		public bool? IsEnabled { get; }

		public string? BaseFolder { get; }

		public bool? UseNormalImagesForTutorial { get; }

		public bool? WatchFolders { get; }

		public bool? LogMissingImagesOnce { get; }

		public PreferencesSnapshot(bool? isEnabled = null, string? baseFolder = null, bool? useNormalImagesForTutorial = null, bool? watchFolders = null, bool? logMissingImagesOnce = null)
		{
			IsEnabled = isEnabled;
			BaseFolder = baseFolder;
			UseNormalImagesForTutorial = useNormalImagesForTutorial;
			WatchFolders = watchFolders;
			LogMissingImagesOnce = logMissingImagesOnce;
		}
	}
}