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.Versioning; using System.Text; using HarmonyLib; using Il2CppInterop.Runtime.InteropTypes.Arrays; using Il2CppScheduleOne.UI.MainMenu; using Il2CppTMPro; using IntroReplacer; using MelonLoader; using MelonLoader.Preferences; using MelonLoader.Utils; using Microsoft.CodeAnalysis; using UnityEngine; using UnityEngine.UI; using UnityEngine.Video; [assembly: CompilationRelaxations(8)] [assembly: RuntimeCompatibility(WrapNonExceptionThrows = true)] [assembly: Debuggable(DebuggableAttribute.DebuggingModes.IgnoreSymbolStoreSequencePoints)] [assembly: AssemblyTitle("IntroReplacer")] [assembly: AssemblyDescription("by Virtunerd")] [assembly: TargetFramework(".NETCoreApp,Version=v6.0", FrameworkDisplayName = ".NET 6.0")] [assembly: MelonInfo(typeof(IntroReplacerMod), "IntroReplacer", "1.0.2", "Virtunerd", null)] [assembly: MelonGame("TVGS", "Schedule I")] [assembly: AssemblyFileVersion("1.0.2")] [assembly: AssemblyInformationalVersion("1.0.2")] [assembly: AssemblyVersion("1.0.2.0")] [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 IntroReplacer { [HarmonyPatch(typeof(Disclaimer))] public static class DisclaimerPatch { private static CanvasGroup group; private static CanvasGroup textGroup; private static bool introStarted; [HarmonyPostfix] [HarmonyPatch("Awake")] public static void AwakePostfix(Disclaimer __instance) { try { if (introStarted) { return; } introStarted = true; group = __instance.Group; textGroup = __instance.TextGroup; if ((Object)(object)group == (Object)null || (Object)(object)textGroup == (Object)null) { MelonLogger.Warning("[PATCH] Disclaimer.Awake: Group or TextGroup is null"); return; } string text = IntroReplacerMod.Mode.Value.ToLowerInvariant(); if (!HasContent(text)) { MelonLogger.Warning("[PATCH] Mode '" + text + "' has no content - leaving the game's own disclaimer untouched."); return; } ((MonoBehaviour)__instance).StopAllCoroutines(); IntroReplacerMod.ArmOverlayWatchdog(group); for (int i = 0; i < ((Component)textGroup).transform.childCount; i++) { ((Component)((Component)textGroup).transform.GetChild(i)).gameObject.SetActive(false); } GameObject gameObject = ((Component)FadeController.CreateContentGroup(((Component)group).gameObject)).gameObject; string text2 = IntroReplacerMod.Mode.Value.ToLowerInvariant(); switch (text2) { case "images": case "graffiti": GraffitiHandler.Setup(gameObject); break; case "text": TextHandler.Setup(gameObject); break; case "video": VideoHandler.Setup(gameObject); break; default: MelonLogger.Warning("[PATCH] Unknown mode: " + text2 + ". Using images."); GraffitiHandler.Setup(gameObject); break; } float duration = __instance.Duration; float bonusTime = ((duration > 1f) ? (duration - 1f) : 0f); MelonCoroutines.Start(DeferredStart(group, textGroup, text2, bonusTime)); group = null; textGroup = null; } catch (Exception ex) { MelonLogger.Error("[PATCH] Disclaimer.Awake failed: " + ex.ToString()); } } private static bool HasContent(string mode) { try { switch (mode) { case "images": case "graffiti": return GraffitiHandler.HasPreloadedContent; case "video": return Directory.Exists(IntroReplacerMod.VideoPath) && Directory.GetFiles(IntroReplacerMod.VideoPath).Length != 0; case "text": return !string.IsNullOrWhiteSpace(IntroReplacerMod.CustomText.Value); default: return GraffitiHandler.HasPreloadedContent; } } catch (Exception ex) { MelonLogger.Warning("[PATCH] HasContent check failed, not taking over: " + ex.Message); return false; } } private static IEnumerator DeferredStart(CanvasGroup group, CanvasGroup textGroup, string mode, float bonusTime) { yield return null; MelonCoroutines.Start(FadeController.RunCustomIntro(group, textGroup, mode, bonusTime)); } } public static class FadeController { internal static CanvasGroup contentGroup; private static bool IsAlive(CanvasGroup cg) { try { return (Object)(object)cg != (Object)null && (Object)(object)((Component)cg).gameObject != (Object)null; } catch { return false; } } public static CanvasGroup CreateContentGroup(GameObject groupGO) { //IL_0005: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Unknown result type (might be due to invalid IL or missing references) //IL_001c: Unknown result type (might be due to invalid IL or missing references) //IL_0023: Unknown result type (might be due to invalid IL or missing references) //IL_002e: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0043: Unknown result type (might be due to invalid IL or missing references) GameObject val = new GameObject("IntroReplacerContent"); val.transform.SetParent(groupGO.transform, false); RectTransform obj = val.AddComponent(); obj.anchorMin = Vector2.zero; obj.anchorMax = Vector2.one; obj.offsetMin = Vector2.zero; obj.offsetMax = Vector2.zero; contentGroup = val.AddComponent(); contentGroup.alpha = 0f; return contentGroup; } public static IEnumerator RunCustomIntro(CanvasGroup group, CanvasGroup textGroup, string mode, float bonusTime = 0f) { float fadeIn = 1f; float display = 3f + bonusTime; float fadeOut = 1f; if (!IsAlive(group) || !IsAlive(textGroup)) { MelonLogger.Warning("[FADE] group or textGroup destroyed. Skipping."); yield break; } group.alpha = 1f; textGroup.alpha = 0f; if (!IsAlive(contentGroup)) { MelonLogger.Warning("[FADE] contentGroup is null. Skipping."); yield break; } switch (mode) { case "images": case "graffiti": yield return GraffitiHandler.PlaySequence(contentGroup, fadeIn, display, fadeOut); break; case "text": yield return RunSingleFade(contentGroup, fadeIn, display, fadeOut); break; case "video": yield return VideoHandler.PlayVideo(contentGroup, fadeIn, display, fadeOut); break; default: yield return RunSingleFade(contentGroup, fadeIn, display, fadeOut); break; } yield return FadeAlpha(group, 1f, 0f, 1f); try { group.blocksRaycasts = false; group.interactable = false; } catch { } finally { IntroReplacerMod.DisarmOverlayWatchdog(); } if (IsAlive(contentGroup)) { Object.Destroy((Object)(object)((Component)contentGroup).gameObject); contentGroup = null; } else { contentGroup = null; } switch (mode) { case "images": case "graffiti": GraffitiHandler.Cleanup(); break; case "video": VideoHandler.Cleanup(); break; } try { if (IsAlive(group)) { ((Component)group).gameObject.SetActive(false); } } catch { } Disclaimer.Shown = true; } public static IEnumerator RunSingleFade(CanvasGroup cg, float fadeIn, float display, float fadeOut) { yield return FadeAlpha(cg, 0f, 1f, fadeIn); yield return (object)new WaitForSeconds(display); yield return FadeAlpha(cg, 1f, 0f, fadeOut); } public static IEnumerator FadeAlpha(CanvasGroup cg, float from, float to, float duration) { if (!IsAlive(cg)) { yield break; } float elapsed = 0f; cg.alpha = from; while (elapsed < duration) { if (!IsAlive(cg)) { yield break; } elapsed += Time.deltaTime; float num = Mathf.Clamp01(elapsed / duration); cg.alpha = Mathf.Lerp(from, to, num); yield return null; } if (IsAlive(cg)) { cg.alpha = to; } } } internal static class GifDecoder { internal struct GifFrame { public byte[] Rgba; public int Width; public int Height; public float Delay; } internal static List Decode(string filePath) { return Decode(File.ReadAllBytes(filePath)); } internal static List Decode(byte[] data) { List list = new List(); int num = 0; if (data.Length < 13) { throw new InvalidDataException("Not a valid GIF file (too short)"); } string text = Encoding.ASCII.GetString(data, 0, 6); if (text != "GIF87a" && text != "GIF89a") { throw new InvalidDataException("Not a valid GIF file: " + text); } num = 6; int num2 = BitConverter.ToUInt16(data, num); num += 2; int num3 = BitConverter.ToUInt16(data, num); num += 2; byte b = data[num++]; num++; num++; bool num4 = (b & 0x80) != 0; int num5 = 1 << (b & 7) + 1; byte[] array = null; if (num4) { array = new byte[num5 * 3]; Buffer.BlockCopy(data, num, array, 0, num5 * 3); num += num5 * 3; } byte[] array2 = new byte[num2 * num3 * 4]; float delay = 0.1f; int num6 = -1; int num7 = 0; while (num < data.Length) { switch (data[num++]) { case 33: if (num >= data.Length) { break; } if (data[num++] == 249) { num++; byte num22 = data[num++]; num7 = (num22 >> 2) & 7; bool num23 = (num22 & 1) != 0; int num24 = BitConverter.ToUInt16(data, num); num += 2; delay = ((num24 <= 0) ? 0.1f : ((float)num24 / 100f)); num6 = (num23 ? data[num++] : (-1)); if (!num23) { num++; } num++; } else { SkipSubBlocks(data, ref num); } continue; case 44: { if (num + 9 > data.Length) { break; } int num8 = BitConverter.ToUInt16(data, num); num += 2; int num9 = BitConverter.ToUInt16(data, num); num += 2; int num10 = BitConverter.ToUInt16(data, num); num += 2; int num11 = BitConverter.ToUInt16(data, num); num += 2; byte b2 = data[num++]; bool num12 = (b2 & 0x80) != 0; bool flag = (b2 & 0x40) != 0; int num13 = (num12 ? (1 << (b2 & 7) + 1) : 0); byte[] array3 = array; if (num12) { array3 = new byte[num13 * 3]; Buffer.BlockCopy(data, num, array3, 0, num13 * 3); num += num13 * 3; } byte[] array4 = null; if (num7 == 3) { array4 = new byte[array2.Length]; Buffer.BlockCopy(array2, 0, array4, 0, array2.Length); } int minCodeSize = data[num++]; byte[] array5 = DecodeLzw(data, ref num, num10 * num11, minCodeSize); if (flag) { array5 = Deinterlace(array5, num10, num11); } for (int i = 0; i < num11; i++) { for (int j = 0; j < num10; j++) { int num14 = i * num10 + j; if (num14 >= array5.Length) { continue; } int num15 = array5[num14] & 0xFF; if (num15 == num6) { continue; } int num16 = num8 + j; int num17 = num9 + i; if (num16 < num2 && num17 < num3) { int num18 = ((num3 - 1 - num17) * num2 + num16) * 4; if (array3 != null && num15 * 3 + 2 < array3.Length) { array2[num18] = array3[num15 * 3]; array2[num18 + 1] = array3[num15 * 3 + 1]; array2[num18 + 2] = array3[num15 * 3 + 2]; array2[num18 + 3] = byte.MaxValue; } } } } byte[] array6 = new byte[array2.Length]; Buffer.BlockCopy(array2, 0, array6, 0, array2.Length); list.Add(new GifFrame { Rgba = array6, Width = num2, Height = num3, Delay = delay }); switch (num7) { case 2: { for (int k = 0; k < num11; k++) { for (int l = 0; l < num10; l++) { int num19 = num8 + l; int num20 = num9 + k; if (num19 < num2 && num20 < num3) { int num21 = ((num3 - 1 - num20) * num2 + num19) * 4; array2[num21] = 0; array2[num21 + 1] = 0; array2[num21 + 2] = 0; array2[num21 + 3] = 0; } } } break; } case 3: if (array4 != null) { Buffer.BlockCopy(array4, 0, array2, 0, array2.Length); } break; } delay = 0.1f; num6 = -1; num7 = 0; continue; } default: SkipSubBlocks(data, ref num); continue; case 59: break; } break; } return list; } private static void SkipSubBlocks(byte[] data, ref int pos) { while (pos < data.Length) { int num = data[pos++]; if (num != 0) { pos += num; if (pos > data.Length) { break; } continue; } break; } } private static byte[] Deinterlace(byte[] src, int width, int height) { byte[] array = new byte[src.Length]; int num = 0; for (int i = 0; i < height; i += 8) { CopyRow(src, array, width, num++, i); } for (int j = 4; j < height; j += 8) { CopyRow(src, array, width, num++, j); } for (int k = 2; k < height; k += 4) { CopyRow(src, array, width, num++, k); } for (int l = 1; l < height; l += 2) { CopyRow(src, array, width, num++, l); } return array; } private static void CopyRow(byte[] src, byte[] dst, int width, int srcRow, int dstRow) { int num = srcRow * width; int num2 = dstRow * width; if (num + width <= src.Length && num2 + width <= dst.Length) { Buffer.BlockCopy(src, num, dst, num2, width); } } private static byte[] DecodeLzw(byte[] data, ref int pos, int pixelCount, int minCodeSize) { List list = new List(pixelCount); while (pos < data.Length) { int num = data[pos++]; if (num == 0 || pos + num > data.Length) { break; } for (int i = 0; i < num; i++) { list.Add(data[pos++]); } } int num2 = 1 << minCodeSize; int num3 = num2 + 1; int num4 = minCodeSize + 1; int num5 = num3 + 1; int num6 = (1 << num4) - 1; int[] array = new int[4096]; byte[] array2 = new byte[4096]; int[] array3 = new int[4096]; for (int j = 0; j < num2; j++) { array[j] = -1; array2[j] = (byte)j; array3[j] = 1; } byte[] array4 = new byte[pixelCount]; int num7 = 0; int num8 = 0; int k = 0; int num9 = 0; byte[] array5 = list.ToArray(); int num10 = -1; byte[] array6 = new byte[4096]; while (num7 < pixelCount) { for (; k < num4; k += 8) { if (num9 >= array5.Length) { goto end_IL_0234; } num8 |= array5[num9++] << k; } int num11 = num8 & num6; num8 >>= num4; k -= num4; if (num11 == num3) { break; } if (num11 == num2) { num4 = minCodeSize + 1; num6 = (1 << num4) - 1; num5 = num3 + 1; num10 = -1; continue; } int num12 = num11; if (num11 >= num5) { if (num10 < 0) { continue; } num12 = num10; } int num13 = 0; int num14 = num12; while (num14 >= 0 && num13 < 4096) { array6[num13++] = array2[num14]; num14 = array[num14]; } int num15 = num13 - 1; while (num15 >= 0 && num7 < pixelCount) { array4[num7++] = array6[num15]; num15--; } if (num11 >= num5 && num7 < pixelCount) { array4[num7++] = array6[num13 - 1]; } if (num10 >= 0 && num5 < 4096) { array[num5] = num10; array2[num5] = array6[num13 - 1]; array3[num5] = ((num10 < 4096) ? array3[num10] : 0) + 1; num5++; if (num5 > num6 && num4 < 12) { num4++; num6 = (1 << num4) - 1; } } num10 = num11; continue; end_IL_0234: break; } return array4; } } public static class GraffitiHandler { private static RawImage rawImage; private static Texture2D[] frames; private static float[] frameDelays; private static bool isAnimated; private static byte[] cachedStaticBytes; private static string cachedStaticName; private static List cachedGifFrames; private static string cachedGifName; private static bool preloaded; internal static bool HasPreloadedContent => preloaded; public static void Preload() { try { string imagesPath = IntroReplacerMod.ImagesPath; if (!Directory.Exists(imagesPath)) { MelonLogger.Warning("[IMAGES] Preload: Folder not found: " + imagesPath); return; } string[] array = (from f in Directory.GetFiles(imagesPath) where f.EndsWith(".png", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".jpg", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".jpeg", StringComparison.OrdinalIgnoreCase) || f.EndsWith(".gif", StringComparison.OrdinalIgnoreCase) orderby f select f).ToArray(); if (array.Length == 0) { MelonLogger.Warning("[IMAGES] Preload: No images found in: " + imagesPath); return; } int num = IntroReplacerMod.LastSlideIndex % array.Length; string text = array[num]; IntroReplacerMod.LastSlideIndex = (num + 1) % array.Length; IntroReplacerMod.SaveState(); if (text.EndsWith(".gif", StringComparison.OrdinalIgnoreCase)) { cachedGifFrames = GifDecoder.Decode(text); cachedGifName = Path.GetFileNameWithoutExtension(text); if (cachedGifFrames.Count == 0) { MelonLogger.Warning("[IMAGES] Preload: GIF has no frames: " + text); return; } } else { cachedStaticBytes = File.ReadAllBytes(text); cachedStaticName = Path.GetFileNameWithoutExtension(text); } preloaded = true; } catch (Exception ex) { MelonLogger.Error("[IMAGES] Preload failed: " + ex.ToString()); } } public static void Setup(GameObject parent) { //IL_0062: Unknown result type (might be due to invalid IL or missing references) //IL_0067: Unknown result type (might be due to invalid IL or missing references) //IL_0079: Unknown result type (might be due to invalid IL or missing references) //IL_00ae: Unknown result type (might be due to invalid IL or missing references) //IL_00c8: Unknown result type (might be due to invalid IL or missing references) //IL_00dd: Unknown result type (might be due to invalid IL or missing references) //IL_00f2: Unknown result type (might be due to invalid IL or missing references) //IL_0103: Unknown result type (might be due to invalid IL or missing references) //IL_0108: Unknown result type (might be due to invalid IL or missing references) //IL_0111: Unknown result type (might be due to invalid IL or missing references) //IL_0116: Unknown result type (might be due to invalid IL or missing references) try { if (!preloaded) { MelonLogger.Warning("[IMAGES] No preloaded data available. Skipping."); return; } if (cachedGifFrames != null && cachedGifFrames.Count > 0) { CreateGifTextures(); } else if (cachedStaticBytes != null) { CreateStaticTexture(); } cachedStaticBytes = null; cachedGifFrames = null; if (frames != null && frames.Length != 0) { GameObject val = new GameObject("IntroImage"); val.transform.SetParent(parent.transform, false); rawImage = val.AddComponent(); rawImage.texture = (Texture)(object)frames[0]; ((Graphic)rawImage).color = new Color(1f, 1f, 1f, 1f); RectTransform component = val.GetComponent(); component.anchorMin = new Vector2(0.5f, 0.5f); component.anchorMax = new Vector2(0.5f, 0.5f); component.pivot = new Vector2(0.5f, 0.5f); RectTransform component2 = parent.GetComponent(); Rect rect = component2.rect; float width = ((Rect)(ref rect)).width; rect = component2.rect; FitToParent(parentH: ((Rect)(ref rect)).height, rt: component, imgW: ((Texture)frames[0]).width, imgH: ((Texture)frames[0]).height, parentW: width); } } catch (Exception ex) { MelonLogger.Error("[IMAGES] Setup failed: " + ex.ToString()); } } private static void CreateStaticTexture() { //IL_0004: Unknown result type (might be due to invalid IL or missing references) //IL_000a: Expected O, but got Unknown Texture2D val = new Texture2D(2, 2, (TextureFormat)4, false); if (!ImageConversion.LoadImage(val, Il2CppStructArray.op_Implicit(cachedStaticBytes), true)) { Object.Destroy((Object)(object)val); MelonLogger.Warning("[IMAGES] Failed to decode static image"); return; } ((Object)val).name = cachedStaticName ?? "intro_static"; frames = (Texture2D[])(object)new Texture2D[1] { val }; frameDelays = new float[1]; isAnimated = false; } private static void CreateGifTextures() { //IL_0050: Unknown result type (might be due to invalid IL or missing references) //IL_0057: Expected O, but got Unknown string text = cachedGifName ?? "intro_gif"; Texture2D[] array = (Texture2D[])(object)new Texture2D[cachedGifFrames.Count]; float[] array2 = new float[cachedGifFrames.Count]; for (int i = 0; i < cachedGifFrames.Count; i++) { GifDecoder.GifFrame gifFrame = cachedGifFrames[i]; Texture2D val = new Texture2D(gifFrame.Width, gifFrame.Height, (TextureFormat)4, false); val.LoadRawTextureData(Il2CppStructArray.op_Implicit(gifFrame.Rgba)); val.Apply(false, true); ((Object)val).name = text + "_f" + i; array[i] = val; array2[i] = gifFrame.Delay; } frames = array; frameDelays = array2; isAnimated = cachedGifFrames.Count > 1; } public static IEnumerator PlaySequence(CanvasGroup contentGroup, float fadeIn, float display, float fadeOut) { if (frames == null || frames.Length == 0 || (Object)(object)rawImage == (Object)null) { MelonLogger.Warning("[IMAGES] Nothing to play. Skipping."); yield break; } yield return FadeController.FadeAlpha(contentGroup, 0f, 1f, fadeIn); if (isAnimated) { float elapsed = 0f; int frameIndex = 0; while (elapsed < display) { rawImage.texture = (Texture)(object)frames[frameIndex]; float frameDelay = frameDelays[frameIndex]; float waited = 0f; while (waited < frameDelay && elapsed < display) { yield return null; float deltaTime = Time.deltaTime; waited += deltaTime; elapsed += deltaTime; } frameIndex = (frameIndex + 1) % frames.Length; } } else { yield return (object)new WaitForSeconds(display); } yield return FadeController.FadeAlpha(contentGroup, 1f, 0f, fadeOut); } private static void FitToParent(RectTransform rt, float imgW, float imgH, float parentW, float parentH) { //IL_004f: Unknown result type (might be due to invalid IL or missing references) if (imgW <= 0f || imgH <= 0f || parentW <= 0f || parentH <= 0f) { return; } float num = imgW; float num2 = imgH; if (num > parentW || num2 > parentH) { float num3 = imgW / imgH; float num4 = parentW / parentH; if (num3 > num4) { num = parentW; num2 = parentW / num3; } else { num2 = parentH; num = parentH * num3; } } rt.sizeDelta = new Vector2(num, num2); } public static void Cleanup() { if (frames != null) { Texture2D[] array = frames; foreach (Texture2D val in array) { if ((Object)(object)val != (Object)null) { Object.Destroy((Object)(object)val); } } frames = null; } frameDelays = null; isAnimated = false; rawImage = null; cachedStaticBytes = null; cachedStaticName = null; cachedGifFrames = null; cachedGifName = null; preloaded = false; } } public class IntroReplacerMod : MelonMod { internal static MelonPreferences_Category Category; internal static MelonPreferences_Entry Mode; internal static MelonPreferences_Entry CustomText; internal static MelonPreferences_Entry VideoVolume; internal const float FadeInDuration = 1f; internal const float DisplayDuration = 3f; internal const float FadeOutDuration = 1f; internal static int LastSlideIndex; internal static int LastTextIndex; internal static int LastVideoIndex; internal static string BasePath; internal static string ImagesPath; internal static string VideoPath; internal static string TextPath; private static string statePath; private static CanvasGroup watchedOverlay; private static float watchdogDeadline; private static bool watchdogFired; private const float WatchdogGraceSeconds = 10f; public override void OnInitializeMelon() { BasePath = Path.Combine(MelonEnvironment.UserDataDirectory, "IntroReplacer"); ImagesPath = Path.Combine(BasePath, "images"); VideoPath = Path.Combine(BasePath, "video"); TextPath = Path.Combine(BasePath, "text"); statePath = Path.Combine(BasePath, ".state"); Category = MelonPreferences.CreateCategory("IntroReplacer", "Intro Replacer"); Mode = Category.CreateEntry("Mode", "images", "Mode", "Active mode: images, text, video", false, false, (ValueValidator)null, (string)null); CustomText = Category.CreateEntry("CustomText", "Welcome to Schedule I", "Custom Text", "Text displayed in text mode", false, false, (ValueValidator)null, (string)null); VideoVolume = Category.CreateEntry("VideoVolume", 90, "Video Volume", "Volume for video playback (0-100)", false, false, (ValueValidator)null, (string)null); LoadState(); EnsureDirectories(); string text = Mode.Value.ToLowerInvariant(); if (text == "images" || text == "graffiti") { GraffitiHandler.Preload(); } } internal static void ArmOverlayWatchdog(CanvasGroup overlay) { watchedOverlay = overlay; watchdogDeadline = Time.realtimeSinceStartup + 1f + 3f + 1f + 10f; watchdogFired = false; } internal static void DisarmOverlayWatchdog() { watchedOverlay = null; } public override void OnUpdate() { if ((Object)(object)watchedOverlay == (Object)null || watchdogFired || Time.realtimeSinceStartup < watchdogDeadline) { return; } watchdogFired = true; try { watchedOverlay.blocksRaycasts = false; watchedOverlay.interactable = false; watchedOverlay.alpha = 0f; MelonLogger.Warning("[WATCHDOG] Intro overlay was still blocking input after the intro should have ended - released it. The intro may have been cut short; the main menu is usable."); } catch (Exception ex) { MelonLogger.Warning("[WATCHDOG] Could not release the intro overlay: " + ex.Message); } finally { watchedOverlay = null; } } internal static void SaveState() { try { File.WriteAllText(statePath, LastSlideIndex + "\n" + LastTextIndex + "\n" + LastVideoIndex); } catch { } } private static void LoadState() { try { if (File.Exists(statePath)) { string[] array = File.ReadAllLines(statePath); if (array.Length >= 1) { int.TryParse(array[0], out LastSlideIndex); } if (array.Length >= 2) { int.TryParse(array[1], out LastTextIndex); } if (array.Length >= 3) { int.TryParse(array[2], out LastVideoIndex); } } } catch { } } private void EnsureDirectories() { try { Directory.CreateDirectory(ImagesPath); Directory.CreateDirectory(VideoPath); Directory.CreateDirectory(TextPath); string path = Path.Combine(TextPath, "custom_text.txt"); if (!File.Exists(path)) { File.WriteAllText(path, "Welcome to Schedule I\n\nThis file is displayed when Mode = text.\nYou can create multiple .txt files in this folder.\nEach game launch shows the next file in rotation.\n\nThis template is only created once and never overwritten."); } } catch (Exception ex) { MelonLogger.Warning("[INIT] Could not create directories: " + ex.Message); } } } public static class TextHandler { public static void Setup(GameObject parent) { //IL_0022: Unknown result type (might be due to invalid IL or missing references) //IL_0027: Unknown result type (might be due to invalid IL or missing references) //IL_0039: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_0091: Unknown result type (might be due to invalid IL or missing references) //IL_00a6: Unknown result type (might be due to invalid IL or missing references) //IL_00b1: Unknown result type (might be due to invalid IL or missing references) //IL_00bb: Unknown result type (might be due to invalid IL or missing references) try { string text = SelectText(); if (string.IsNullOrWhiteSpace(text)) { MelonLogger.Warning("[TEXT] Text is empty. Skipping."); return; } GameObject val = new GameObject("CustomText"); val.transform.SetParent(parent.transform, false); TextMeshProUGUI obj = val.AddComponent(); ((TMP_Text)obj).text = text; ((TMP_Text)obj).fontSize = 36f; ((TMP_Text)obj).alignment = (TextAlignmentOptions)514; ((Graphic)obj).color = new Color(1f, 1f, 1f, 1f); ((TMP_Text)obj).enableWordWrapping = true; RectTransform component = val.GetComponent(); component.anchorMin = new Vector2(0.1f, 0.2f); component.anchorMax = new Vector2(0.9f, 0.8f); component.offsetMin = Vector2.zero; component.offsetMax = Vector2.zero; } catch (Exception ex) { MelonLogger.Error("[TEXT] Setup failed: " + ex.ToString()); } } private static string SelectText() { string textPath = IntroReplacerMod.TextPath; if (Directory.Exists(textPath)) { string[] array = (from f in Directory.GetFiles(textPath, "*.txt") orderby f select f).ToArray(); if (array.Length != 0) { int num = IntroReplacerMod.LastTextIndex % array.Length; string path = array[num]; IntroReplacerMod.LastTextIndex = (num + 1) % array.Length; IntroReplacerMod.SaveState(); return File.ReadAllText(path).Trim(); } } return IntroReplacerMod.CustomText.Value; } } public static class VideoHandler { private static VideoPlayer videoPlayer; private static RawImage rawImage; private static RenderTexture renderTexture; private static RectTransform imageRT; public static void Setup(GameObject parent) { //IL_0013: Unknown result type (might be due to invalid IL or missing references) //IL_0018: Unknown result type (might be due to invalid IL or missing references) //IL_002a: Unknown result type (might be due to invalid IL or missing references) //IL_004e: Unknown result type (might be due to invalid IL or missing references) //IL_0071: Unknown result type (might be due to invalid IL or missing references) //IL_008a: Unknown result type (might be due to invalid IL or missing references) //IL_00a3: Unknown result type (might be due to invalid IL or missing references) try { string text = SelectVideo(); if (text != null) { GameObject val = new GameObject("VideoImage"); val.transform.SetParent(parent.transform, false); rawImage = val.AddComponent(); ((Graphic)rawImage).color = new Color(1f, 1f, 1f, 1f); imageRT = val.GetComponent(); imageRT.anchorMin = new Vector2(0.5f, 0.5f); imageRT.anchorMax = new Vector2(0.5f, 0.5f); imageRT.pivot = new Vector2(0.5f, 0.5f); videoPlayer = parent.AddComponent(); videoPlayer.playOnAwake = false; videoPlayer.renderMode = (VideoRenderMode)2; videoPlayer.source = (VideoSource)1; videoPlayer.url = "file:///" + text.Replace("\\", "/"); videoPlayer.isLooping = false; videoPlayer.audioOutputMode = (VideoAudioOutputMode)2; videoPlayer.SetDirectAudioMute((ushort)0, true); videoPlayer.SetDirectAudioVolume((ushort)0, Mathf.Clamp01((float)IntroReplacerMod.VideoVolume.Value / 100f)); } } catch (Exception ex) { MelonLogger.Error("[VIDEO] Setup failed: " + ex.ToString()); } } public static IEnumerator PlayVideo(CanvasGroup contentGroup, float fadeIn, float display, float fadeOut) { if ((Object)(object)videoPlayer == (Object)null || (Object)(object)rawImage == (Object)null) { MelonLogger.Warning("[VIDEO] VideoPlayer or RawImage is null. Skipping."); yield break; } videoPlayer.Prepare(); float timeout = 10f; float waited = 0f; while (!videoPlayer.isPrepared && waited < timeout) { waited += Time.deltaTime; yield return null; } if (!videoPlayer.isPrepared) { MelonLogger.Warning("[VIDEO] Prepare timed out after " + timeout + "s"); yield break; } RenderTexture placeholderRT = new RenderTexture(64, 64, 0); placeholderRT.Create(); videoPlayer.targetTexture = placeholderRT; videoPlayer.Play(); float dimTimeout = 5f; float dimWaited = 0f; int vidW = 0; int vidH = 0; while (dimWaited < dimTimeout) { vidW = (int)videoPlayer.width; vidH = (int)videoPlayer.height; if (vidW > 0 && vidH > 0) { break; } dimWaited += Time.deltaTime; yield return null; } if (vidW <= 0 || vidH <= 0) { MelonLogger.Warning("[VIDEO] Could not determine dimensions after " + dimTimeout + "s"); vidW = 1920; vidH = 1080; } renderTexture = new RenderTexture(vidW, vidH, 0); renderTexture.Create(); videoPlayer.targetTexture = renderTexture; rawImage.texture = (Texture)(object)renderTexture; placeholderRT.Release(); Object.Destroy((Object)(object)placeholderRT); Canvas componentInParent = ((Component)rawImage).GetComponentInParent(); float num = (((Object)(object)componentInParent != (Object)null && componentInParent.scaleFactor > 0f) ? componentInParent.scaleFactor : 1f); RectTransform component = ((Component)((Component)rawImage).transform.parent).GetComponent(); Rect rect = component.rect; float width = ((Rect)(ref rect)).width; rect = component.rect; float height = ((Rect)(ref rect)).height; FitToParent(imageRT, (float)vidW / num, (float)vidH / num, width, height); videoPlayer.SetDirectAudioMute((ushort)0, false); yield return FadeController.FadeAlpha(contentGroup, 0f, 1f, fadeIn); float elapsed = 0f; while ((Object)(object)videoPlayer != (Object)null && videoPlayer.isPlaying && elapsed < display) { elapsed += Time.deltaTime; yield return null; } if ((Object)(object)videoPlayer != (Object)null && videoPlayer.isPlaying) { videoPlayer.Stop(); } yield return FadeController.FadeAlpha(contentGroup, 1f, 0f, fadeOut); } private static void FitToParent(RectTransform rt, float imgW, float imgH, float parentW, float parentH) { //IL_0047: Unknown result type (might be due to invalid IL or missing references) if (!(imgW <= 0f) && !(imgH <= 0f) && !(parentW <= 0f) && !(parentH <= 0f)) { float num = imgW; float num2 = imgH; if (num > parentW || num2 > parentH) { float num3 = Mathf.Min(parentW / num, parentH / num2); num *= num3; num2 *= num3; } rt.sizeDelta = new Vector2(num, num2); } } private static string SelectVideo() { string videoPath = IntroReplacerMod.VideoPath; if (!Directory.Exists(videoPath)) { MelonLogger.Warning("[VIDEO] Folder not found: " + videoPath); return null; } string[] array = (from f in Directory.GetFiles(videoPath, "*.mp4") orderby f select f).ToArray(); if (array.Length == 0) { MelonLogger.Warning("[VIDEO] No .mp4 files found in: " + videoPath); return null; } int num = IntroReplacerMod.LastVideoIndex % array.Length; string result = array[num]; IntroReplacerMod.LastVideoIndex = (num + 1) % array.Length; IntroReplacerMod.SaveState(); return result; } public static void Cleanup() { if ((Object)(object)videoPlayer != (Object)null) { videoPlayer.Stop(); Object.Destroy((Object)(object)videoPlayer); videoPlayer = null; } if ((Object)(object)renderTexture != (Object)null) { renderTexture.Release(); Object.Destroy((Object)(object)renderTexture); renderTexture = null; } rawImage = null; imageRT = null; } } }