From 72cf6aced7f58d95db79eb5f6c9aa8aafe764944 Mon Sep 17 00:00:00 2001 From: WUGqnwvMQPzl <36147447+WUGqnwvMQPzl@users.noreply.github.com> Date: Fri, 13 Feb 2026 23:30:13 +0800 Subject: [PATCH 1/7] [+] TitleScreenVideo --- AquaMai.Mods/Fancy/TitleScreenVideo.cs | 215 +++++++++++++++++++++++++ 1 file changed, 215 insertions(+) create mode 100644 AquaMai.Mods/Fancy/TitleScreenVideo.cs diff --git a/AquaMai.Mods/Fancy/TitleScreenVideo.cs b/AquaMai.Mods/Fancy/TitleScreenVideo.cs new file mode 100644 index 00000000..26173b2e --- /dev/null +++ b/AquaMai.Mods/Fancy/TitleScreenVideo.cs @@ -0,0 +1,215 @@ +using AquaMai.Config.Attributes; +using AquaMai.Core.Attributes; +using AquaMai.Core.Helpers; +using HarmonyLib; +using MAI2.Util; +using Manager; +using MelonLoader; +using Monitor; +using Process; +using System.Collections.Generic; +using UnityEngine; +using UnityEngine.Video; + +namespace AquaMai.Mods.Fancy; + +[ConfigSection( + "标题画面视频", + en: "Plays custom video on title screen, just like in the good ol' days", + zh: "复刻 bud 代之前的标题界面视频动画")] +[EnableGameVersion(24000)] +public class TitleScreenVideo +{ + [ConfigEntry( + en: "Title Video / Audio File Path (without file extensions, mp4 video and acb/awb audio are supported)", + zh: "标题视音频文件路径,不包括文件后缀名(视频为 mp4 格式,音频为 acb/awb 格式")] + private static readonly string VideoPath = "LocalAssets/DX_title"; + + private static GameObject[] _movieObjects = new GameObject[2]; + private static VideoPlayer[] _videoPlayers = new VideoPlayer[2]; + + private static List[] _disabledCompoments = [[], []]; + + private static bool _isVideoPrepared = false; + private static bool _isAudioPrepared = false; + + [HarmonyPostfix] + [HarmonyPatch(typeof(AdvertiseProcess), "OnStart")] + public static void OnStart_Postfix(AdvertiseMonitor[] ____monitors) + { + var moviePref = Resources.Load("Process/AdvertiseCommercial/AdvertiseCommercialProcess").transform.Find("Canvas/Main/MovieMask").gameObject; + + for (int i = 0; i < ____monitors.Length; ++i) + { + var monitor = ____monitors[i]; + + // Disable fade out cover on cir (and maybe future version?) + if (GameInfo.GameVersion >= 26000) + monitor.transform.Find("Canvas/Main/UI_ADV_Title/Null_all/out_cover")?.gameObject.SetActive(false); + + var titleLoop = monitor.transform.Find("Canvas/Main/UI_ADV_Title/Null_all/TitleLoop"); + + // Disable all elements on the original title screen + for (int j = 0; j < titleLoop.childCount; ++j) + { + var obj = titleLoop.GetChild(j).gameObject; + if (obj.activeSelf) + { + obj.SetActive(false); + _disabledCompoments[i].Add(obj.name); + } + } + + _movieObjects[i] = UnityEngine.Object.Instantiate(moviePref, titleLoop); + _movieObjects[i].GetComponent().color = new Color(0, 0, 0, 0); + + _videoPlayers[i] = _movieObjects[i].AddComponent(); + _videoPlayers[i].url = FileSystem.ResolvePath(VideoPath + ".mp4"); + _videoPlayers[i].playOnAwake = false; + _videoPlayers[i].isLooping = false; + _videoPlayers[i].renderMode = VideoRenderMode.MaterialOverride; + _videoPlayers[i].audioOutputMode = VideoAudioOutputMode.None; + + var movieSprite = _movieObjects[i].transform.Find("Movie").gameObject.GetComponent(); + + _videoPlayers[i].prepareCompleted += (source) => + { + // Prevent autoplay + source.Pause(); + source.time = 0; + + // Setting the video player size + var vWidth = source.width; + var vHeight = source.height; + + var calWidth = vHeight > vWidth ? (1080 * vWidth / vHeight) : 1080; + var calHeight = vHeight > vWidth ? 1080 : (1080 * vHeight / vWidth); + + movieSprite.size = new Vector2(calWidth, calHeight); + + _isVideoPrepared = true; + }; + + _videoPlayers[i].errorReceived += (source, err) => + { + _isVideoPrepared = false; + + MelonLogger.Error($"[TitleScreenVideo] Failed to load video file: {err}"); + }; + + _videoPlayers[i].Prepare(); + + var movieMaterial = new Material(Shader.Find("Sprites/Default")); + movieSprite.material = movieMaterial; + _videoPlayers[i].targetMaterialRenderer = movieSprite; + } + + _isAudioPrepared = SoundManager.MusicPrepareForFileName(VideoPath); + if (!_isAudioPrepared) + MelonLogger.Warning("[TitleScreenVideo] Failed to load audio file, game's default title jingle will be played instead"); + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(AdvertiseProcess), "OnUpdate")] + public static void OnUpdate_Postfix(AdvertiseProcess.AdvertiseSequence ____state, AdvertiseMonitor[] ____monitors) + { + switch (____state) + { + case AdvertiseProcess.AdvertiseSequence.Logo: + // Re-enable original title screen elements if the video is unavailable + // yeah calling it early so the switch is unnoticeable + if (!_isVideoPrepared) + { + for (int i = 0; i < ____monitors.Length; ++i) + { + _movieObjects[i].SetActive(false); + + var titleLoop = ____monitors[i].transform.Find("Canvas/Main/UI_ADV_Title/Null_all/TitleLoop"); + foreach (string name in _disabledCompoments[i]) + titleLoop.Find(name).gameObject.SetActive(true); + + _disabledCompoments[i] = []; + } + } + break; + case AdvertiseProcess.AdvertiseSequence.TransitionOut: + if (_isVideoPrepared) + { + for (int i = 0; i < ____monitors.Length; ++i) + { + // Stop yelling "maimai deluxe" I'm tired to hearing it + SoundManager.StopVoice(i); + + _videoPlayers[i].Play(); + + if (_isAudioPrepared) + { + // Stop game's original title music and plays our own + SoundManager.StopJingle(i); + SoundManager.StartMusic(); + } + } + } + break; + } + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(AdvertiseProcess), "LeaveAdvertise")] + public static void LeaveAdvertise_Postfix() + { + if (_isAudioPrepared) + { + // Stop and unloads title music + SoundManager.StopMusic(); + Singleton.Instance.UnloadCueSheet(1); + } + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(AdvertiseProcess), "OnRelease")] + public static void OnRelease_Prefix(AdvertiseMonitor[] ____monitors) + { + for (int i = 0; i < ____monitors.Length; ++i) + { + if (_videoPlayers[i] != null) + { + _videoPlayers[i].prepareCompleted -= null; + _videoPlayers[i].errorReceived -= null; + UnityEngine.Object.Destroy(_videoPlayers[i]); + _videoPlayers[i] = null; + } + + if (_movieObjects[i] != null) + { + UnityEngine.Object.Destroy(_movieObjects[i]); + _movieObjects[i] = null; + } + } + + // Resets status + _isVideoPrepared = false; + _isAudioPrepared = false; + _disabledCompoments = [[], []]; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(AdvertiseMonitor), "AllStop")] + public static bool Monitor_AllStop_Prefix() + { + // So uhh... When I was testing the feature, this method makes title screen suddently go black before transition + // I don't like the sudden cutout so I disabled it, not sure about the side effect or compatibility though + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(AdvertiseMonitor), "IsTitleAnimationEnd")] + public static bool Monitor_IsTitleAnimationEnd_Prefix(ref bool __result, int ___monitorIndex) + { + if (!_isVideoPrepared) + return true; + + __result = !_videoPlayers[___monitorIndex].isPlaying && _videoPlayers[___monitorIndex].frame >= (long) _videoPlayers[___monitorIndex].frameCount - 1; + return false; + } +} From 14f593dc2c986735f342ac2e85b58f198e9f9d89 Mon Sep 17 00:00:00 2001 From: WUGqnwvMQPzl <36147447+WUGqnwvMQPzl@users.noreply.github.com> Date: Mon, 16 Feb 2026 10:56:08 +0800 Subject: [PATCH 2/7] [O] materials --- AquaMai.Mods/Fancy/TitleScreenVideo.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/Fancy/TitleScreenVideo.cs b/AquaMai.Mods/Fancy/TitleScreenVideo.cs index 26173b2e..3e4407bb 100644 --- a/AquaMai.Mods/Fancy/TitleScreenVideo.cs +++ b/AquaMai.Mods/Fancy/TitleScreenVideo.cs @@ -27,6 +27,7 @@ public class TitleScreenVideo private static GameObject[] _movieObjects = new GameObject[2]; private static VideoPlayer[] _videoPlayers = new VideoPlayer[2]; + private static Material[] _videoMaterials = new Material[2]; private static List[] _disabledCompoments = [[], []]; @@ -99,8 +100,8 @@ public static void OnStart_Postfix(AdvertiseMonitor[] ____monitors) _videoPlayers[i].Prepare(); - var movieMaterial = new Material(Shader.Find("Sprites/Default")); - movieSprite.material = movieMaterial; + _videoMaterials[i] = new Material(Shader.Find("Sprites/Default")); + movieSprite.material = _videoMaterials[i]; _videoPlayers[i].targetMaterialRenderer = movieSprite; } @@ -172,6 +173,12 @@ public static void OnRelease_Prefix(AdvertiseMonitor[] ____monitors) { for (int i = 0; i < ____monitors.Length; ++i) { + if (_videoMaterials[i] != null) + { + UnityEngine.Object.Destroy(_videoMaterials[i]); + _videoMaterials[i] = null; + } + if (_videoPlayers[i] != null) { _videoPlayers[i].prepareCompleted -= null; From eb0bdb0a64f97628a49f1bdff96a288c05edde3e Mon Sep 17 00:00:00 2001 From: WUGqnwvMQPzl <36147447+WUGqnwvMQPzl@users.noreply.github.com> Date: Mon, 16 Feb 2026 11:16:39 +0800 Subject: [PATCH 3/7] [O] clean up unnecessary code i guess? --- AquaMai.Mods/Fancy/TitleScreenVideo.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/AquaMai.Mods/Fancy/TitleScreenVideo.cs b/AquaMai.Mods/Fancy/TitleScreenVideo.cs index 3e4407bb..6a267d48 100644 --- a/AquaMai.Mods/Fancy/TitleScreenVideo.cs +++ b/AquaMai.Mods/Fancy/TitleScreenVideo.cs @@ -181,8 +181,6 @@ public static void OnRelease_Prefix(AdvertiseMonitor[] ____monitors) if (_videoPlayers[i] != null) { - _videoPlayers[i].prepareCompleted -= null; - _videoPlayers[i].errorReceived -= null; UnityEngine.Object.Destroy(_videoPlayers[i]); _videoPlayers[i] = null; } From a73f516e700e97c0544675e209de62afd6ea61fb Mon Sep 17 00:00:00 2001 From: WUGqnwvMQPzl <36147447+WUGqnwvMQPzl@users.noreply.github.com> Date: Mon, 16 Feb 2026 19:51:25 +0800 Subject: [PATCH 4/7] [O] make video ready flag thread safe i guess --- AquaMai.Mods/Fancy/TitleScreenVideo.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/AquaMai.Mods/Fancy/TitleScreenVideo.cs b/AquaMai.Mods/Fancy/TitleScreenVideo.cs index 6a267d48..9d03d936 100644 --- a/AquaMai.Mods/Fancy/TitleScreenVideo.cs +++ b/AquaMai.Mods/Fancy/TitleScreenVideo.cs @@ -8,6 +8,7 @@ using Monitor; using Process; using System.Collections.Generic; +using System.Threading; using UnityEngine; using UnityEngine.Video; @@ -31,7 +32,9 @@ public class TitleScreenVideo private static List[] _disabledCompoments = [[], []]; - private static bool _isVideoPrepared = false; + private static int _videoPreparedCount = 0; + private static bool IsVideoPrepared => _videoPreparedCount >= 2; + private static bool _isAudioPrepared = false; [HarmonyPostfix] @@ -88,13 +91,11 @@ public static void OnStart_Postfix(AdvertiseMonitor[] ____monitors) movieSprite.size = new Vector2(calWidth, calHeight); - _isVideoPrepared = true; + Interlocked.Increment(ref _videoPreparedCount); }; _videoPlayers[i].errorReceived += (source, err) => { - _isVideoPrepared = false; - MelonLogger.Error($"[TitleScreenVideo] Failed to load video file: {err}"); }; @@ -119,7 +120,7 @@ public static void OnUpdate_Postfix(AdvertiseProcess.AdvertiseSequence ____state case AdvertiseProcess.AdvertiseSequence.Logo: // Re-enable original title screen elements if the video is unavailable // yeah calling it early so the switch is unnoticeable - if (!_isVideoPrepared) + if (!IsVideoPrepared) { for (int i = 0; i < ____monitors.Length; ++i) { @@ -134,7 +135,7 @@ public static void OnUpdate_Postfix(AdvertiseProcess.AdvertiseSequence ____state } break; case AdvertiseProcess.AdvertiseSequence.TransitionOut: - if (_isVideoPrepared) + if (IsVideoPrepared) { for (int i = 0; i < ____monitors.Length; ++i) { @@ -193,7 +194,7 @@ public static void OnRelease_Prefix(AdvertiseMonitor[] ____monitors) } // Resets status - _isVideoPrepared = false; + Interlocked.Exchange(ref _videoPreparedCount, 0); _isAudioPrepared = false; _disabledCompoments = [[], []]; } @@ -211,7 +212,7 @@ public static bool Monitor_AllStop_Prefix() [HarmonyPatch(typeof(AdvertiseMonitor), "IsTitleAnimationEnd")] public static bool Monitor_IsTitleAnimationEnd_Prefix(ref bool __result, int ___monitorIndex) { - if (!_isVideoPrepared) + if (!IsVideoPrepared) return true; __result = !_videoPlayers[___monitorIndex].isPlaying && _videoPlayers[___monitorIndex].frame >= (long) _videoPlayers[___monitorIndex].frameCount - 1; From 279d89a39006428cb337cb96d25c37e9532add08 Mon Sep 17 00:00:00 2001 From: WUGqnwvMQPzl <36147447+WUGqnwvMQPzl@users.noreply.github.com> Date: Tue, 10 Mar 2026 01:29:57 +0800 Subject: [PATCH 5/7] [O] config field fix --- AquaMai.Mods/Fancy/TitleScreenVideo.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AquaMai.Mods/Fancy/TitleScreenVideo.cs b/AquaMai.Mods/Fancy/TitleScreenVideo.cs index 9d03d936..ba3782d5 100644 --- a/AquaMai.Mods/Fancy/TitleScreenVideo.cs +++ b/AquaMai.Mods/Fancy/TitleScreenVideo.cs @@ -23,8 +23,8 @@ public class TitleScreenVideo { [ConfigEntry( en: "Title Video / Audio File Path (without file extensions, mp4 video and acb/awb audio are supported)", - zh: "标题视音频文件路径,不包括文件后缀名(视频为 mp4 格式,音频为 acb/awb 格式")] - private static readonly string VideoPath = "LocalAssets/DX_title"; + zh: "标题视音频文件路径,不包括文件后缀名(视频为 mp4 格式,音频为 acb/awb 格式)")] + public static readonly string VideoPath = "LocalAssets/DX_title"; private static GameObject[] _movieObjects = new GameObject[2]; private static VideoPlayer[] _videoPlayers = new VideoPlayer[2]; From 65522970c24bf521822c6b931e6b7a6686edcaaf Mon Sep 17 00:00:00 2001 From: WUGqnwvMQPzl <36147447+WUGqnwvMQPzl@users.noreply.github.com> Date: Tue, 10 Mar 2026 01:43:04 +0800 Subject: [PATCH 6/7] [+] add option to skip allnet logo if title video is loaded --- AquaMai.Mods/Fancy/TitleScreenVideo.cs | 87 ++++++++++++++++---------- 1 file changed, 54 insertions(+), 33 deletions(-) diff --git a/AquaMai.Mods/Fancy/TitleScreenVideo.cs b/AquaMai.Mods/Fancy/TitleScreenVideo.cs index ba3782d5..6e25b680 100644 --- a/AquaMai.Mods/Fancy/TitleScreenVideo.cs +++ b/AquaMai.Mods/Fancy/TitleScreenVideo.cs @@ -26,6 +26,11 @@ public class TitleScreenVideo zh: "标题视音频文件路径,不包括文件后缀名(视频为 mp4 格式,音频为 acb/awb 格式)")] public static readonly string VideoPath = "LocalAssets/DX_title"; + [ConfigEntry( + en: "Skip the SEGA / All.Net logo when custom title video file is loaded", + zh: "自定标题视频成功加载后,跳过 SEGA / All.Net 标志动画")] + public static readonly bool SkipLogo = false; + private static GameObject[] _movieObjects = new GameObject[2]; private static VideoPlayer[] _videoPlayers = new VideoPlayer[2]; private static Material[] _videoMaterials = new Material[2]; @@ -115,44 +120,22 @@ public static void OnStart_Postfix(AdvertiseMonitor[] ____monitors) [HarmonyPatch(typeof(AdvertiseProcess), "OnUpdate")] public static void OnUpdate_Postfix(AdvertiseProcess.AdvertiseSequence ____state, AdvertiseMonitor[] ____monitors) { - switch (____state) + if (____state == AdvertiseProcess.AdvertiseSequence.TransitionOut && IsVideoPrepared) { - case AdvertiseProcess.AdvertiseSequence.Logo: - // Re-enable original title screen elements if the video is unavailable - // yeah calling it early so the switch is unnoticeable - if (!IsVideoPrepared) - { - for (int i = 0; i < ____monitors.Length; ++i) - { - _movieObjects[i].SetActive(false); + for (int i = 0; i < ____monitors.Length; ++i) + { + // Stop yelling "maimai deluxe" I'm tired to hearing it + SoundManager.StopVoice(i); - var titleLoop = ____monitors[i].transform.Find("Canvas/Main/UI_ADV_Title/Null_all/TitleLoop"); - foreach (string name in _disabledCompoments[i]) - titleLoop.Find(name).gameObject.SetActive(true); + _videoPlayers[i].Play(); - _disabledCompoments[i] = []; - } - } - break; - case AdvertiseProcess.AdvertiseSequence.TransitionOut: - if (IsVideoPrepared) + if (_isAudioPrepared) { - for (int i = 0; i < ____monitors.Length; ++i) - { - // Stop yelling "maimai deluxe" I'm tired to hearing it - SoundManager.StopVoice(i); - - _videoPlayers[i].Play(); - - if (_isAudioPrepared) - { - // Stop game's original title music and plays our own - SoundManager.StopJingle(i); - SoundManager.StartMusic(); - } - } + // Stop game's original title music and plays our own + SoundManager.StopJingle(i); + SoundManager.StartMusic(); } - break; + } } } @@ -218,4 +201,42 @@ public static bool Monitor_IsTitleAnimationEnd_Prefix(ref bool __result, int ___ __result = !_videoPlayers[___monitorIndex].isPlaying && _videoPlayers[___monitorIndex].frame >= (long) _videoPlayers[___monitorIndex].frameCount - 1; return false; } + + [HarmonyPrefix] + [HarmonyPatch(typeof(AdvertiseMonitor), "PlayLogo")] + public static bool Monitor_PlayLogo_Prefix(int ___monitorIndex, GameObject ____eventModeObject, CanvasGroup ___Main) + { + if (!IsVideoPrepared) + { + // Re-enable original title screen elements if the video is unavailable + // doing this early so the transition will be less noticable + _movieObjects[___monitorIndex].SetActive(false); + + var titleLoop = ___Main.transform.Find("UI_ADV_Title/Null_all/TitleLoop"); + foreach (string name in _disabledCompoments[___monitorIndex]) + titleLoop.Find(name).gameObject.SetActive(true); + + _disabledCompoments[___monitorIndex] = []; + + return true; + } + + if (!SkipLogo) + return true; + + ____eventModeObject.SetActive(GameManager.IsEventMode); + ___Main.transform.Find("UI_ADV_SegaAllNet").gameObject.SetActive(false); + return false; + } + + [HarmonyPrefix] + [HarmonyPatch(typeof(AdvertiseMonitor), "IsLogoAnimationEnd")] + public static bool Monitor_IsLogoAnimationEnd_Prefix(ref bool __result) + { + if (!IsVideoPrepared || !SkipLogo) + return true; + + __result = true; + return false; + } } From 2f14037a0f0e819f8bb99414593ae2c4402aad32 Mon Sep 17 00:00:00 2001 From: WUGqnwvMQPzl <36147447+WUGqnwvMQPzl@users.noreply.github.com> Date: Tue, 10 Mar 2026 02:19:51 +0800 Subject: [PATCH 7/7] [+] hide copyright on the title screen --- AquaMai.Mods/Fancy/TitleScreenVideo.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/AquaMai.Mods/Fancy/TitleScreenVideo.cs b/AquaMai.Mods/Fancy/TitleScreenVideo.cs index 6e25b680..a4465081 100644 --- a/AquaMai.Mods/Fancy/TitleScreenVideo.cs +++ b/AquaMai.Mods/Fancy/TitleScreenVideo.cs @@ -31,6 +31,11 @@ public class TitleScreenVideo zh: "自定标题视频成功加载后,跳过 SEGA / All.Net 标志动画")] public static readonly bool SkipLogo = false; + [ConfigEntry( + en: "Hide copyright information on the bottom of the title screen", + zh: "隐藏标题画面底部的版权信息")] + public static readonly bool HideCopyright = false; + private static GameObject[] _movieObjects = new GameObject[2]; private static VideoPlayer[] _videoPlayers = new VideoPlayer[2]; private static Material[] _videoMaterials = new Material[2]; @@ -56,6 +61,10 @@ public static void OnStart_Postfix(AdvertiseMonitor[] ____monitors) if (GameInfo.GameVersion >= 26000) monitor.transform.Find("Canvas/Main/UI_ADV_Title/Null_all/out_cover")?.gameObject.SetActive(false); + // Hide copyright information + if (HideCopyright) + monitor.transform.Find("Canvas/Main/UI_ADV_Title/Null_all/Licence").gameObject.SetActive(false); + var titleLoop = monitor.transform.Find("Canvas/Main/UI_ADV_Title/Null_all/TitleLoop"); // Disable all elements on the original title screen