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