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