Decompiled source of SkipIntro v1.1.1

Mods/SkipIntro.dll

Decompiled 3 weeks ago
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Security;
using System.Security.Permissions;
using HarmonyLib;
using Il2Cpp;
using Il2CppInterop.Common;
using Il2CppInterop.Runtime;
using Il2CppInterop.Runtime.InteropTypes;
using Il2CppSystem;
using Il2CppTMPro;
using MelonLoader;
using MelonLoader.Preferences;
using Microsoft.CodeAnalysis;
using SkipIntro;
using SpellBrigade.ModMenu;
using SpellBrigade.Shared;
using UnityEngine;
using UnityEngine.Localization;
using UnityEngine.Localization.Settings;

[assembly: CompilationRelaxations(8)]
[assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)]
[assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)]
[assembly: MelonInfo(typeof(SkipIntroMod), "Skip Intro", "1.1.1", "Relsev", null)]
[assembly: MelonGame("BoltBlasterGames", "TheSpellBrigade")]
[assembly: MelonOptionalDependencies(new string[] { "ModMenu" })]
[assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")]
[assembly: AssemblyCompany("SkipIntro")]
[assembly: AssemblyConfiguration("Release")]
[assembly: AssemblyFileVersion("1.1.1.0")]
[assembly: AssemblyInformationalVersion("1.1.1+1856b18f4bf94ea33a9b586b39e9ee6f647e5453")]
[assembly: AssemblyProduct("SkipIntro")]
[assembly: AssemblyTitle("SkipIntro")]
[assembly: SecurityPermission(SecurityAction.RequestMinimum, SkipVerification = true)]
[assembly: AssemblyVersion("1.1.1.0")]
[module: UnverifiableCode]
[module: RefSafetyRules(11)]
namespace Microsoft.CodeAnalysis
{
	[CompilerGenerated]
	[Embedded]
	internal sealed class EmbeddedAttribute : Attribute
	{
	}
}
namespace System.Runtime.CompilerServices
{
	[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 SpellBrigade.Shared
{
	internal static class Hooks
	{
		public static bool Patch(Harmony harmony, Instance log, Type target, string method, Type patchClass, string prefix = null, string postfix = null)
		{
			return Patch(harmony, log, AccessTools.Method(target, method, (Type[])null, (Type[])null), patchClass, prefix, postfix, target.Name + "." + method);
		}

		public static bool Patch(Harmony harmony, Instance log, MethodBase original, Type patchClass, string prefix = null, string postfix = null, string name = null)
		{
			//IL_011a: Unknown result type (might be due to invalid IL or missing references)
			//IL_0130: Unknown result type (might be due to invalid IL or missing references)
			if (name == null)
			{
				name = ((original != null) ? (original.DeclaringType?.Name + "." + original.Name) : ((prefix ?? postfix) + " target"));
			}
			try
			{
				if (original == null)
				{
					throw new MissingMethodException(name + " not found");
				}
				List<string> list = SharedCodeGuard.FindMethodsSharingCode(original);
				if (list.Count > 0)
				{
					log.Warning($"skipped {name}: its native code is shared with {list.Count} other method(s), patching would break them (e.g. {string.Join(", ", list.GetRange(0, Math.Min(5, list.Count)))})");
					return false;
				}
				harmony.Patch(original, (prefix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(AccessTools.Method(patchClass, prefix, (Type[])null, (Type[])null)), (postfix == null) ? ((HarmonyMethod)null) : new HarmonyMethod(AccessTools.Method(patchClass, postfix, (Type[])null, (Type[])null)), (HarmonyMethod)null, (HarmonyMethod)null, (HarmonyMethod)null);
				return true;
			}
			catch (Exception ex)
			{
				log.Error($"failed to hook {name} — this part is disabled. {ex.GetType().Name}: {ex.Message}");
				return false;
			}
		}
	}
	internal static class SharedCodeGuard
	{
		private static Dictionary<IntPtr, List<IntPtr>> _methodsByCode;

		public static List<string> FindMethodsSharingCode(MethodBase generatedMethod)
		{
			List<string> list = new List<string>();
			if (!(Il2CppInteropUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(generatedMethod)?.GetValue(null) is IntPtr intPtr) || intPtr == IntPtr.Zero)
			{
				return list;
			}
			IntPtr intPtr2 = Marshal.ReadIntPtr(intPtr);
			if (intPtr2 == IntPtr.Zero)
			{
				return list;
			}
			if (_methodsByCode == null)
			{
				_methodsByCode = BuildIndex();
			}
			if (!_methodsByCode.TryGetValue(intPtr2, out var value))
			{
				return list;
			}
			foreach (IntPtr item in value)
			{
				if (item != intPtr)
				{
					list.Add(Describe(item));
				}
			}
			return list;
		}

		private static string Describe(IntPtr method)
		{
			IntPtr intPtr = IL2CPP.il2cpp_method_get_class(method);
			string text = IL2CPP.il2cpp_class_get_namespace_(intPtr);
			return (string.IsNullOrEmpty(text) ? IL2CPP.il2cpp_class_get_name_(intPtr) : (text + "." + IL2CPP.il2cpp_class_get_name_(intPtr))) + "::" + IL2CPP.il2cpp_method_get_name_(method);
		}

		private unsafe static Dictionary<IntPtr, List<IntPtr>> BuildIndex()
		{
			Dictionary<IntPtr, List<IntPtr>> dictionary = new Dictionary<IntPtr, List<IntPtr>>();
			uint num = 0u;
			IntPtr* ptr = IL2CPP.il2cpp_domain_get_assemblies(IL2CPP.il2cpp_domain_get(), ref num);
			for (uint num2 = 0u; num2 < num; num2++)
			{
				IntPtr intPtr = IL2CPP.il2cpp_assembly_get_image(ptr[num2]);
				uint num3 = IL2CPP.il2cpp_image_get_class_count(intPtr);
				for (uint num4 = 0u; num4 < num3; num4++)
				{
					IntPtr intPtr2 = IL2CPP.il2cpp_image_get_class(intPtr, num4);
					if (intPtr2 == IntPtr.Zero)
					{
						continue;
					}
					IntPtr zero = IntPtr.Zero;
					IntPtr intPtr3;
					while ((intPtr3 = IL2CPP.il2cpp_class_get_methods(intPtr2, ref zero)) != IntPtr.Zero)
					{
						IntPtr intPtr4 = Marshal.ReadIntPtr(intPtr3);
						if (!(intPtr4 == IntPtr.Zero))
						{
							if (!dictionary.TryGetValue(intPtr4, out var value))
							{
								value = (dictionary[intPtr4] = new List<IntPtr>(1));
							}
							value.Add(intPtr3);
						}
					}
				}
			}
			return dictionary;
		}
	}
	internal sealed class Localizer
	{
		private readonly string[] _keys;

		private readonly Dictionary<string, string[]> _table;

		private readonly Func<Instance> _log;

		private readonly Dictionary<string, bool> _fontSupport = new Dictionary<string, bool>();

		private readonly List<(TMP_Text text, Func<string> value)> _bound = new List<(TMP_Text, Func<string>)>();

		private readonly List<Action> _listeners = new List<Action>();

		private string[] _current;

		private string _code;

		private TMP_Text _fontSample;

		private bool _subscribed;

		public Localizer(string[] keys, Dictionary<string, string[]> table, Func<Instance> log)
		{
			_keys = keys;
			_table = table;
			_log = log;
		}

		public string Get(string key)
		{
			if (_current == null || _code != LocaleCode())
			{
				_current = Resolve(_fontSample);
			}
			int num = Array.IndexOf(_keys, key);
			if (num < 0 || num >= _current.Length)
			{
				return key;
			}
			return _current[num];
		}

		public void Bind(TMP_Text text, Func<string> value, TMP_Text fontSample = null)
		{
			if (!((Object)(object)text == (Object)null))
			{
				EnsureSubscribed();
				if (_current == null)
				{
					_current = Resolve(fontSample ?? text);
				}
				text.text = value();
				_bound.Add((text, value));
			}
		}

		public void Bind(TMP_Text text, string key, TMP_Text fontSample = null)
		{
			Bind(text, () => Get(key), fontSample);
		}

		public void OnLanguageChanged(Action listener)
		{
			EnsureSubscribed();
			_listeners.Add(listener);
		}

		public void UseFont(TMP_Text sample)
		{
			_fontSample = sample;
			_current = null;
			EnsureSubscribed();
		}

		private void EnsureSubscribed()
		{
			if (_subscribed)
			{
				return;
			}
			_subscribed = true;
			try
			{
				LocalizationSettings.SelectedLocaleChanged += Action<Locale>.op_Implicit((Action<Locale>)delegate
				{
					Refresh();
				});
			}
			catch (Exception ex)
			{
				Instance obj = _log();
				if (obj != null)
				{
					obj.Warning("[lang] can't follow language changes: " + ex.Message);
				}
			}
		}

		private void Refresh()
		{
			_bound.RemoveAll(((TMP_Text text, Func<string> value) b) => (Object)(object)b.text == (Object)null);
			_current = Resolve((_bound.Count > 0) ? _bound[0].text : _fontSample);
			foreach (var (val, func) in _bound)
			{
				val.text = func();
			}
			foreach (Action listener in _listeners)
			{
				try
				{
					listener();
				}
				catch (Exception ex)
				{
					Instance obj = _log();
					if (obj != null)
					{
						obj.Warning("[lang] " + ex.Message);
					}
				}
			}
		}

		private static string LocaleCode()
		{
			try
			{
				Locale selectedLocale = LocalizationSettings.SelectedLocale;
				return ((selectedLocale != null) ? selectedLocale.Identifier.Code : null) ?? "en";
			}
			catch
			{
				return "en";
			}
		}

		private string[] Resolve(TMP_Text fontSample)
		{
			string text = (_code = LocaleCode());
			string text2 = TableKey(text);
			if (text2 != "en" && (Object)(object)fontSample != (Object)null && !FontHasAll(fontSample, text2))
			{
				Instance obj = _log();
				if (obj != null)
				{
					obj.Warning("[lang] font lacks glyphs for '" + text + "', using English");
				}
				text2 = "en";
			}
			return _table[text2];
		}

		private string TableKey(string code)
		{
			code = code.ToLowerInvariant().Replace('_', '-');
			string text = (code.StartsWith("zh") ? ((code.Contains("hant") || code.Contains("tw") || code.Contains("hk")) ? "zh-hant" : "zh-hans") : (code.StartsWith("pt") ? (code.Contains("br") ? "pt-br" : "pt") : ((!code.StartsWith("es")) ? code.Split('-')[0] : ((code == "es" || code.Contains("es-es")) ? "es" : "es-mx"))));
			if (_table.ContainsKey(text))
			{
				return text;
			}
			string text2 = text.Split('-')[0];
			if (!_table.ContainsKey(text2))
			{
				return "en";
			}
			return text2;
		}

		private bool FontHasAll(TMP_Text sample, string key)
		{
			if (_fontSupport.TryGetValue(key, out var value))
			{
				return value;
			}
			value = true;
			try
			{
				TMP_FontAsset font = sample.font;
				if ((Object)(object)font != (Object)null)
				{
					string[] array = _table[key];
					foreach (string text in array)
					{
						foreach (char c in text)
						{
							if (c > '\u007f' && !char.IsWhiteSpace(c) && !font.HasCharacter(c, true, true))
							{
								value = false;
								break;
							}
						}
						if (!value)
						{
							break;
						}
					}
				}
			}
			catch
			{
				value = true;
			}
			_fontSupport[key] = value;
			return value;
		}
	}
}
namespace SkipIntro
{
	internal static class MenuPage
	{
		[MethodImpl(MethodImplOptions.NoInlining)]
		public static void Register(MelonPreferences_Entry<bool> enabled, Action save)
		{
			Menu.AddPage("SkipIntro", "Skip Intro").Toggle((Func<string>)(() => Strings.Get("enabled")), (Func<bool>)(() => enabled.Value), (Action<bool>)delegate(bool v)
			{
				if (enabled.Value != v)
				{
					enabled.Value = v;
					save();
				}
			}, (bool?)enabled.DefaultValue);
		}
	}
	public class SkipIntroMod : MelonMod
	{
		public static Instance Log;

		private static MelonPreferences_Entry<bool> _enabled;

		public override void OnInitializeMelon()
		{
			Log = ((MelonBase)this).LoggerInstance;
			MelonPreferences_Category category = MelonPreferences.CreateCategory("SkipIntro", "Skip Intro");
			_enabled = category.CreateEntry<bool>("Enabled", true, "Skip Intro", "Пропускать логотипы и вступительные ролики", false, false, (ValueValidator)null, (string)null);
			category.SaveToFile(false);
			if (MelonBase.FindMelon("Mod Menu", "Relsev") != null)
			{
				try
				{
					MenuPage.Register(_enabled, delegate
					{
						category.SaveToFile(false);
					});
				}
				catch (Exception ex)
				{
					Log.Warning("Mod Menu page: " + ex.Message);
				}
			}
			if (Hooks.Patch(((MelonBase)this).HarmonyInstance, Log, typeof(OpeningFlowManager), "Start", typeof(SkipIntroMod), null, "StartPostfix"))
			{
				Log.Msg("ready — logos and intro videos are skipped");
			}
			else
			{
				Log.Warning("intro stays");
			}
		}

		private static void StartPostfix(OpeningFlowManager __instance)
		{
			if (_enabled.Value)
			{
				MelonCoroutines.Start(SkipAll(__instance));
			}
		}

		private static IEnumerator SkipAll(OpeningFlowManager manager)
		{
			HashSet<IntPtr> skipped = new HashSet<IntPtr>();
			for (int frame = 0; frame < 600; frame++)
			{
				if (!((Object)(object)manager != (Object)null))
				{
					break;
				}
				try
				{
					OpeningFlow openingFlow = manager.openingFlow;
					IOpeningStep val = ((openingFlow != null) ? openingFlow.currentStep : null);
					while (val != null && skipped.Add(((Il2CppObjectBase)val).Pointer))
					{
						openingFlow.SkipCurrentStep();
						val = openingFlow.currentStep;
					}
				}
				catch (Exception value)
				{
					Log.Error($"skip failed, rest of the intro plays as usual: {value}");
					yield break;
				}
				yield return null;
			}
			if (skipped.Count > 0)
			{
				Log.Msg($"intro skipped ({skipped.Count} step(s))");
			}
		}
	}
	internal static class Strings
	{
		public const string Enabled = "enabled";

		private static readonly string[] Keys = new string[1] { "enabled" };

		private static readonly Dictionary<string, string[]> Table = new Dictionary<string, string[]>
		{
			["en"] = new string[1] { "Skip logos and intro videos" },
			["ru"] = new string[1] { "Пропускать логотипы и вступительные ролики" },
			["uk"] = new string[1] { "Пропускати логотипи й вступні ролики" },
			["de"] = new string[1] { "Logos und Intro-Videos überspringen" },
			["fr"] = new string[1] { "Passer les logos et les vidéos d'intro" },
			["it"] = new string[1] { "Salta loghi e video introduttivi" },
			["nl"] = new string[1] { "Logo's en introvideo's overslaan" },
			["pl"] = new string[1] { "Pomijaj loga i filmy wprowadzające" },
			["pt-br"] = new string[1] { "Pular logos e vídeos de abertura" },
			["pt"] = new string[1] { "Saltar logótipos e vídeos de introdução" },
			["es"] = new string[1] { "Saltar logotipos y vídeos de introducción" },
			["es-mx"] = new string[1] { "Omitir logotipos y videos de introducción" },
			["ja"] = new string[1] { "ロゴとオープニングムービーをスキップ" },
			["ko"] = new string[1] { "로고와 인트로 영상 건너뛰기" },
			["zh-hans"] = new string[1] { "跳过标志和开场视频" },
			["zh-hant"] = new string[1] { "跳過標誌和開場影片" },
			["th"] = new string[1] { "ข\u0e49ามโลโก\u0e49และว\u0e34ด\u0e35โอเป\u0e34ดเกม" }
		};

		private static readonly Localizer Text = new Localizer(Keys, Table, () => SkipIntroMod.Log);

		public static string Get(string key)
		{
			return Text.Get(key);
		}
	}
}