diff --git a/AquaMai/AquaMai.csproj b/AquaMai/AquaMai.csproj index a415eba0..aecd3838 100644 --- a/AquaMai/AquaMai.csproj +++ b/AquaMai/AquaMai.csproj @@ -308,7 +308,7 @@ - + diff --git a/AquaMai/AquaMai.toml b/AquaMai/AquaMai.toml index 9fe19af5..aa756d4d 100644 --- a/AquaMai/AquaMai.toml +++ b/AquaMai/AquaMai.toml @@ -22,8 +22,10 @@ SinglePlayer=true SkipToMusicSelection=false # Set the version string displayed at the top-right corner of the screen CustomVersionString="" +# Deprecated: Use `LoadAssetsPng` instead +# LoadJacketPng=true # Load Jacket image from folder "LocalAssets" and filename "{MusicID}.png" for self-made charts -LoadJacketPng=true +LoadAssetsPng=true # Use the png jacket above as BGA if BGA is not found for self-made charts # Use together with `LoadJacketPng` LoadLocalBga=true diff --git a/AquaMai/AquaMai.zh.toml b/AquaMai/AquaMai.zh.toml index b31dc3b8..b744b626 100644 --- a/AquaMai/AquaMai.zh.toml +++ b/AquaMai/AquaMai.zh.toml @@ -22,8 +22,10 @@ SinglePlayer=true SkipToMusicSelection=false # 把右上角的版本更改为自定义文本 CustomVersionString="" +# 已弃用,请使用 LoadAssetsPng +# LoadJacketPng=true # 通过游戏目录下 `LocalAssets\000000(歌曲 ID).png` 加载封面,自制谱用 -LoadJacketPng=true +LoadAssetsPng=true # 如果没有 dat 格式的 BGA 的话,就用歌曲的封面做背景,而不是显示迪拉熊的笑脸 # 请和 `LoadJacketPng` 一起用 LoadLocalBga=true diff --git a/AquaMai/Config.cs b/AquaMai/Config.cs index aa9de9e0..00b4b561 100644 --- a/AquaMai/Config.cs +++ b/AquaMai/Config.cs @@ -1,4 +1,5 @@ using System.Diagnostics.CodeAnalysis; +using Tomlet.Attributes; namespace AquaMai { @@ -23,6 +24,7 @@ public class UXConfig public bool SkipWarningScreen { get; set; } public bool SinglePlayer { get; set; } public bool SkipToMusicSelection { get; set; } + public bool LoadAssetsPng { get; set; } public bool LoadJacketPng { get; set; } public bool LoadAssetBundleWithoutManifest { get; set; } public bool QuickSkip { get; set; } diff --git a/AquaMai/Main.cs b/AquaMai/Main.cs index e6596b6f..35b1bc19 100644 --- a/AquaMai/Main.cs +++ b/AquaMai/Main.cs @@ -73,6 +73,10 @@ public override void OnInitializeMelon() // Read AquaMai.toml to load settings AppConfig = TomletMain.To(System.IO.File.ReadAllText("AquaMai.toml")); + // Migrate old settings + AppConfig.UX.LoadAssetsPng = AppConfig.UX.LoadAssetsPng || AppConfig.UX.LoadJacketPng; + AppConfig.UX.LoadJacketPng = false; + // Fixes that does not have side effects // These don't need to be configurable diff --git a/AquaMai/UX/LoadAssetsPng.cs b/AquaMai/UX/LoadAssetsPng.cs new file mode 100644 index 00000000..c778ea96 --- /dev/null +++ b/AquaMai/UX/LoadAssetsPng.cs @@ -0,0 +1,171 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using HarmonyLib; +using UnityEngine; +using System.Text.RegularExpressions; +using MAI2.Util; +using Manager; +using MelonLoader; +using Monitor; +using Object = UnityEngine.Object; + +namespace AquaMai.UX; + +public class LoadAssetsPng +{ + private static string[] imageExts = [".jpg", ".png", ".jpeg"]; + private static Dictionary jacketPaths = new(); + private static Dictionary tabTitlePaths = new(); + private static Dictionary localAssetsContents = new(); + + [HarmonyPrefix] + [HarmonyPatch(typeof(DataManager), "LoadMusicBase")] + public static void LoadMusicPostfix(List ____targetDirs) + { + foreach (var aDir in ____targetDirs) + { + if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\jacket"))) + foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\jacket"))) + { + if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; + var idStr = Path.GetFileName(file).Substring("ui_jacket_".Length, 6); + jacketPaths[idStr] = file; + } + + if (Directory.Exists(Path.Combine(aDir, @"Common\Sprites\Tab\Title"))) + foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"Common\Sprites\Tab\Title"))) + { + if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; + tabTitlePaths[Path.GetFileNameWithoutExtension(file).ToLowerInvariant()] = file; + } + } + + MelonLogger.Msg($"[LoadAssetsPng] Loaded {jacketPaths.Count} Jacket, {tabTitlePaths.Count} Tab Titles from AssetBundleImages."); + + if (Directory.Exists(Path.Combine(Environment.CurrentDirectory, "LocalAssets"))) + foreach (var laFile in Directory.EnumerateFiles(Path.Combine(Environment.CurrentDirectory, "LocalAssets"))) + { + if (!imageExts.Contains(Path.GetExtension(laFile).ToLowerInvariant())) continue; + localAssetsContents[Path.GetFileNameWithoutExtension(laFile).ToLowerInvariant()] = laFile; + } + + MelonLogger.Msg($"[LoadAssetsPng] Loaded {localAssetsContents.Count} LocalAssets."); + } + + private static string GetJacketPath(string id) + { + return localAssetsContents.TryGetValue(id, out var laPath) ? laPath : jacketPaths.GetValueOrDefault(id); + } + + public static Texture2D GetJacketTexture2D(string id) + { + var path = GetJacketPath(id); + if (path == null) + { + return null; + } + + var texture = new Texture2D(1, 1); + texture.LoadImage(File.ReadAllBytes(path)); + return texture; + } + + public static Texture2D GetJacketTexture2D(int id) + { + return GetJacketTexture2D($"{id:000000}"); + } + + /* + [HarmonyPatch] + public static class TabTitleLoader + { + public static IEnumerable TargetMethods() + { + // Fxxk unity + // game load tab title by call Resources.Load directly + // patching Resources.Load need this stuff + // var method = typeof(Resources).GetMethods(BindingFlags.Public | BindingFlags.Static).First(it => it.Name == "Load" && it.IsGenericMethod).MakeGenericMethod(typeof(Sprite)); + // return [method]; + // but it not work, game will blackscreen if add prefix or postfix + // + // patching AssetBundleManager.LoadAsset will lead game memory error + // return [AccessTools.Method(typeof(AssetBundleManager), "LoadAsset", [typeof(string)], [typeof(Object)])]; + // and this is not work because game not using this + // + // we load them manually after game load and no need to hook the load progress + } + + public static bool Prefix(string path, ref Object __result) + { + if (!path.StartsWith("Common/Sprites/Tab/Title/")) return true; + var filename = Path.GetFileNameWithoutExtension(path).ToLowerInvariant(); + var locPath = localAssetsContents.TryGetValue(filename, out var laPath) ? laPath : tabTitlePaths.GetValueOrDefault(filename); + if (locPath is null) return true; + + var texture = new Texture2D(1, 1); + texture.LoadImage(File.ReadAllBytes(locPath)); + __result = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)); + MelonLogger.Msg($"GetTabTitleSpritePrefix {locPath} {__result}"); + return false; + } + } + */ + + [HarmonyPostfix] + [HarmonyPatch(typeof(MusicSelectMonitor), "Initialize")] + public static void TabTitleLoader(MusicSelectMonitor __instance, Dictionary ____genreSprite, Dictionary ____versionSprite) + { + var genres = Singleton.Instance.GetMusicGenres(); + foreach (var (id, genre) in genres) + { + if (____genreSprite.GetValueOrDefault(id) is not null) continue; + var filename = genre.FileName.ToLowerInvariant(); + var locPath = localAssetsContents.TryGetValue(filename, out var laPath) ? laPath : tabTitlePaths.GetValueOrDefault(filename); + if (locPath is null) continue; + var texture = new Texture2D(1, 1); + texture.LoadImage(File.ReadAllBytes(locPath)); + ____genreSprite[id] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)); + } + + var versions = Singleton.Instance.GetMusicVersions(); + foreach (var (id, version) in versions) + { + if (____versionSprite.GetValueOrDefault(id) is not null) continue; + var filename = version.FileName.ToLowerInvariant(); + var locPath = localAssetsContents.TryGetValue(filename, out var laPath) ? laPath : tabTitlePaths.GetValueOrDefault(filename); + if (locPath is null) continue; + var texture = new Texture2D(1, 1); + texture.LoadImage(File.ReadAllBytes(locPath)); + ____versionSprite[id] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f)); + } + } + + [HarmonyPatch] + public static class JacketLoader + { + public static IEnumerable TargetMethods() + { + var AM = typeof(AssetManager); + return [AM.GetMethod("GetJacketThumbTexture2D", [typeof(string)]), AM.GetMethod("GetJacketTexture2D", [typeof(string)])]; + } + + public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance) + { + var matches = Regex.Matches(filename, @"UI_Jacket_(\d+)(_s)?\.png"); + if (matches.Count < 1) + { + return true; + } + + var id = matches[0].Groups[1].Value; + + var texture = GetJacketTexture2D(id); + __result = texture ?? __instance.LoadAsset($"Jacket/UI_Jacket_{id}.png"); + + return false; + } + } +} diff --git a/AquaMai/UX/LoadJacketPng.cs b/AquaMai/UX/LoadJacketPng.cs deleted file mode 100644 index fd35956f..00000000 --- a/AquaMai/UX/LoadJacketPng.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using HarmonyLib; -using UnityEngine; -using System.Text.RegularExpressions; -using Manager; -using MelonLoader; - -namespace AquaMai.UX -{ - public class LoadJacketPng - { - [HarmonyPatch] - public static class Loader - { - public static IEnumerable TargetMethods() - { - var AM = typeof(AssetManager); - return new[] { AM.GetMethod("GetJacketThumbTexture2D", new[] { typeof(string) }), AM.GetMethod("GetJacketTexture2D", new[] { typeof(string) }) }; - } - - public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance) - { - var matches = Regex.Matches(filename, @"UI_Jacket_(\d+)(_s)?\.png"); - if (matches.Count < 1) - { - return true; - } - - var id = matches[0].Groups[1].Value; - - var texture = GetJacketTexture2D(id); - __result = texture ?? __instance.LoadAsset($"Jacket/UI_Jacket_{id}.png"); - - return false; - } - } - - private static string[] imageExts = [".jpg", ".png", ".jpeg"]; - private static Regex localAssetsJacketExt = new(@"(\d{6})\.(png|jpg|jpeg)"); - private static Dictionary jacketPaths = new(); - - [HarmonyPrefix] - [HarmonyPatch(typeof(DataManager), "LoadMusicBase")] - public static void LoadMusicPostfix(List ____targetDirs) - { - foreach (var aDir in ____targetDirs) - { - if (!Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\jacket"))) continue; - foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\jacket"))) - { - if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue; - var idStr = Path.GetFileName(file).Substring("ui_jacket_".Length, 6); - jacketPaths[idStr] = file; - } - } - - if (Directory.Exists(Path.Combine(Environment.CurrentDirectory, "LocalAssets"))) - foreach (var laFile in Directory.EnumerateFiles(Path.Combine(Environment.CurrentDirectory, "LocalAssets"))) - { - var match = localAssetsJacketExt.Match(Path.GetFileName(laFile)); - if (!match.Success) continue; - jacketPaths[match.Groups[1].Value] = laFile; - } - - MelonLogger.Msg($"[LoadJacketPng] Loaded {jacketPaths.Count} custom jacket images."); - } - - private static string GetJacketPath(string id) - { - return jacketPaths.GetValueOrDefault(id); - } - - public static Texture2D GetJacketTexture2D(string id) - { - var path = GetJacketPath(id); - if (path == null) - { - return null; - } - - var texture = new Texture2D(1, 1); - texture.LoadImage(File.ReadAllBytes(path)); - return texture; - } - - public static Texture2D GetJacketTexture2D(int id) - { - return GetJacketTexture2D($"{id:000000}"); - } - } -} diff --git a/AquaMai/UX/LoadLocalBga.cs b/AquaMai/UX/LoadLocalBga.cs index 2cbc4bc0..e1388ab9 100644 --- a/AquaMai/UX/LoadLocalBga.cs +++ b/AquaMai/UX/LoadLocalBga.cs @@ -20,7 +20,7 @@ public static void LoadLocalBgaAwake(GameObject ____movieMaskObj) var moviePath = string.Format(Singleton.Instance.GetMovieDataPath($"{music.movieName.id:000000}") + ".dat"); if (!moviePath.Contains("dummy")) return; - var jacket = LoadJacketPng.GetJacketTexture2D(music.movieName.id); + var jacket = LoadAssetsPng.GetJacketTexture2D(music.movieName.id); if (jacket is null) { MelonLogger.Msg("No jacket found for music " + music);