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