From c648493a9efd9d6f33ba93bf98b3c47a19cbae6d Mon Sep 17 00:00:00 2001
From: Minepig <30530115+Minepig@users.noreply.github.com>
Date: Fri, 4 Oct 2024 13:47:07 +0800
Subject: [PATCH] =?UTF-8?q?=E8=87=AA=E5=AE=9A=E4=B9=89=E7=9A=AE=E8=82=A4?=
=?UTF-8?q?=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
AquaMai/AquaMai.csproj | 5 +
AquaMai/CustomSkin/CustomNoteSkin.cs | 314 +++++++++++++++++++++++++++
AquaMai/Main.cs | 9 +
3 files changed, 328 insertions(+)
create mode 100644 AquaMai/CustomSkin/CustomNoteSkin.cs
diff --git a/AquaMai/AquaMai.csproj b/AquaMai/AquaMai.csproj
index a83253de..3adf00f5 100644
--- a/AquaMai/AquaMai.csproj
+++ b/AquaMai/AquaMai.csproj
@@ -292,12 +292,14 @@ DEBUG
+
+
@@ -311,6 +313,9 @@ DEBUG
+
+
+
True
True
diff --git a/AquaMai/CustomSkin/CustomNoteSkin.cs b/AquaMai/CustomSkin/CustomNoteSkin.cs
new file mode 100644
index 00000000..24b0b891
--- /dev/null
+++ b/AquaMai/CustomSkin/CustomNoteSkin.cs
@@ -0,0 +1,314 @@
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Reflection;
+using HarmonyLib;
+using MelonLoader;
+using Monitor;
+using Monitor.Game;
+using Process;
+using UnityEngine;
+
+namespace AquaMai.CustomSkin;
+
+public class CustomNoteSkin
+{
+ private static readonly List ImageExts = [".jpg", ".png", ".jpeg"];
+ private static readonly List SlideFanFields = ["_normalSlideFan", "_eachSlideFan", "_breakSlideFan", "_breakSlideFanEff"];
+
+ private static Sprite customOutline;
+ private static Sprite[,] customSlideFan = new Sprite[4, 11];
+
+ private static bool LoadIntoGameNoteImageContainer(string fieldName, int? idx1, int? idx2, Texture2D texture)
+ {
+ // 先确定确实有这个 Field, 如果没有的话可以直接跳过这个文件
+ var fieldTraverse = Traverse.Create(typeof(GameNoteImageContainer)).Field(fieldName);
+ if (!fieldTraverse.FieldExists())
+ {
+ MelonLogger.Msg($"[CustomNoteSkin] Cannot found field {fieldName}");
+ return false;
+ }
+
+ var fieldType = fieldTraverse.GetValueType();
+ if (!idx1.HasValue)
+ {
+ // 目标 Field 应当是单个 Sprite
+ if (fieldType != typeof(Sprite))
+ {
+ MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} is a {fieldType.Name}, not a Sprite");
+ return false;
+ }
+ var target = fieldTraverse.GetValue();
+ var pivot = new Vector2(target.pivot.x / target.rect.width, target.pivot.y / target.rect.height);
+ var custom = Sprite.Create(
+ texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
+ 0, SpriteMeshType.Tight, target.border
+ );
+ fieldTraverse.SetValue(custom);
+ }
+ else if (!idx2.HasValue)
+ {
+ // 目标 Field 是一维数组
+ if (fieldType != typeof(Sprite[]))
+ {
+ MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} is a {fieldType.Name}, not a Sprite[]");
+ return false;
+ }
+ var targetArray = fieldTraverse.GetValue();
+ var target = targetArray[idx1.Value];
+ var pivot = new Vector2(target.pivot.x / target.rect.width, target.pivot.y / target.rect.height);
+ var custom = Sprite.Create(
+ texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
+ 0, SpriteMeshType.Tight, target.border
+ );
+ targetArray[idx1.Value] = custom;
+ }
+ else
+ {
+ // 目标 Field 是二维数组
+ if (fieldType != typeof(Sprite[,]))
+ {
+ MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} is a {fieldType.Name}, not a Sprite[,]");
+ return false;
+ }
+ var targetArray = fieldTraverse.GetValue();
+ var target = targetArray[idx1.Value, idx2.Value];
+ var pivot = new Vector2(target.pivot.x / target.rect.width, target.pivot.y / target.rect.height);
+ var custom = Sprite.Create(
+ texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
+ 0, SpriteMeshType.Tight, target.border
+ );
+ targetArray[idx1.Value, idx2.Value] = custom;
+ }
+
+ return true;
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(GameNotePrefabContainer), "Initialize")]
+ private static void LoadNoteSkin()
+ {
+ if (!Directory.Exists(Path.Combine(Environment.CurrentDirectory, "Skins"))) return;
+
+ foreach (var laFile in Directory.EnumerateFiles(Path.Combine(Environment.CurrentDirectory, "Skins")))
+ {
+ if (!ImageExts.Contains(Path.GetExtension(laFile).ToLowerInvariant())) continue;
+ var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
+ texture.LoadImage(File.ReadAllBytes(laFile));
+
+ var name = Path.GetFileNameWithoutExtension(laFile);
+ var args = name.Split('_');
+ // 文件名的格式是 XXXXXXXX_A_B 表示 GameNoteImageContainer._XXXXXXXX[A, B]
+ // 视具体情况, A, B 可能不存在
+ var fieldName = '_' + args[0];
+ int? idx1 = (args.Length < 2)? null : (int.TryParse(args[1], out var temp) ? temp : null);
+ int? idx2 = (args.Length < 3)? null : (int.TryParse(args[2], out temp) ? temp : null);
+
+ Traverse traverse;
+
+ if (fieldName == "_outline")
+ {
+ customOutline = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(0.5f, 0.5f), 1f);
+ MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
+ continue;
+ }
+
+ if (SlideFanFields.Contains(fieldName))
+ {
+ if (!idx1.HasValue)
+ {
+ MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index");
+ continue;
+ }
+ var i = SlideFanFields.IndexOf(fieldName);
+ customSlideFan[i, idx1.Value] = Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), new Vector2(1f, 0.5f), 1f);
+ MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
+ continue;
+ }
+
+ if (fieldName == "_touchJust")
+ {
+ traverse = Traverse.Create(GameNotePrefabContainer.TouchTapB);
+ var noticeObject = traverse.Field("NoticeObject").Value;
+ var target = noticeObject.GetComponent();
+ var pivot = new Vector2(
+ target.sprite.pivot.x / target.sprite.rect.width,
+ target.sprite.pivot.y / target.sprite.rect.height
+ );
+ var custom = Sprite.Create(
+ texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
+ 0, SpriteMeshType.Tight, target.sprite.border
+ );
+ target.sprite = custom;
+
+ traverse = Traverse.Create(GameNotePrefabContainer.TouchTapC);
+ noticeObject = traverse.Field("NoticeObject").Value;
+ noticeObject.GetComponent().sprite = custom;
+ MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
+ continue;
+ }
+
+ if (fieldName == "_touchHold")
+ {
+ if (!idx1.HasValue)
+ {
+ MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index");
+ continue;
+ }
+ traverse = Traverse.Create(GameNotePrefabContainer.TouchHoldC);
+ var target = traverse.Field("ColorsObject").Value;
+ var renderer = target[idx1.Value];
+ var pivot = new Vector2(
+ renderer.sprite.pivot.x / renderer.sprite.rect.width,
+ renderer.sprite.pivot.y / renderer.sprite.rect.height
+ );
+ var custom = Sprite.Create(
+ texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
+ 0, SpriteMeshType.Tight, renderer.sprite.border
+ );
+ renderer.sprite = custom;
+ MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
+ continue;
+ }
+
+ if (fieldName == "_normalTouchBorder")
+ {
+ if (!idx1.HasValue)
+ {
+ MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index");
+ continue;
+ }
+ traverse = Traverse.Create(GameNotePrefabContainer.TouchReserve);
+ var target = traverse.Field("_reserveSingleSprite").Value;
+ var targetSprite = target[idx1.Value - 2];
+ var pivot = new Vector2(
+ targetSprite.pivot.x / targetSprite.rect.width,
+ targetSprite.pivot.y / targetSprite.rect.height
+ );
+ target[idx1.Value - 2] = Sprite.Create(
+ texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
+ 0, SpriteMeshType.Tight, targetSprite.border
+ );
+ MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
+ continue;
+ }
+
+ if (fieldName == "_eachTouchBorder")
+ {
+ if (!idx1.HasValue)
+ {
+ MelonLogger.Msg($"[CustomNoteSkin] Field {fieldName} needs a index");
+ continue;
+ }
+ traverse = Traverse.Create(GameNotePrefabContainer.TouchReserve);
+ var target = traverse.Field("_reserveEachSprite").Value;
+ var targetSprite = target[idx1.Value - 2];
+ var pivot = new Vector2(
+ targetSprite.pivot.x / targetSprite.rect.width,
+ targetSprite.pivot.y / targetSprite.rect.height
+ );
+ target[idx1.Value - 2] = Sprite.Create(
+ texture, new Rect(0, 0, texture.width, texture.height), pivot, 1f,
+ 0, SpriteMeshType.Tight, targetSprite.border
+ );
+ MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
+ continue;
+ }
+
+ if (LoadIntoGameNoteImageContainer(fieldName, idx1, idx2, texture))
+ {
+ MelonLogger.Msg($"[CustomNoteSkin] Successfully loaded {name}");
+ }
+ }
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(GameCtrl), "Initialize")]
+ private static void ChangeOutlineTexture(GameObject ____guideEndPointObj)
+ {
+ if (____guideEndPointObj != null && customOutline != null)
+ {
+ ____guideEndPointObj.GetComponent().sprite = customOutline;
+ }
+ }
+
+ [HarmonyPostfix]
+ [HarmonyPatch(typeof(SlideFan), "Initialize")]
+ private static void ChangeFanTexture(
+ SpriteRenderer[] ____spriteLines, SpriteRenderer[] ____effectSprites, bool ___BreakFlag, bool ___EachFlag
+ )
+ {
+ Vector3 position;
+ Sprite sprite;
+ if (___BreakFlag)
+ {
+ for (var i = 0; i < 11; i++)
+ {
+ sprite = customSlideFan[2, i];
+ if (sprite != null)
+ {
+ ____spriteLines[2 * i].sprite = sprite;
+ position = ____spriteLines[2 * i].transform.localPosition;
+ ____spriteLines[2 * i].transform.localPosition = new Vector3(0, position.y, position.z);
+ ____spriteLines[2 * i].color = Color.white;
+
+ ____spriteLines[2 * i + 1].sprite = sprite;
+ position = ____spriteLines[2 * i + 1].transform.localPosition;
+ ____spriteLines[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z);
+ ____spriteLines[2 * i + 1].color = Color.white;
+ }
+ sprite = customSlideFan[3, i];
+ if (sprite != null)
+ {
+ ____effectSprites[2 * i].sprite = sprite;
+ position = ____effectSprites[2 * i].transform.localPosition;
+ ____effectSprites[2 * i].transform.localPosition = new Vector3(0, position.y, position.z);
+ ____effectSprites[2 * i].color = Color.white;
+
+ ____effectSprites[2 * i + 1].sprite = sprite;
+ position = ____effectSprites[2 * i + 1].transform.localPosition;
+ ____effectSprites[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z);
+ ____effectSprites[2 * i + 1].color = Color.white;
+ }
+ }
+ }
+ else if (___EachFlag)
+ {
+ for (var i = 0; i < 11; i++)
+ {
+ sprite = customSlideFan[1, i];
+ if (sprite != null)
+ {
+ ____spriteLines[2 * i].sprite = sprite;
+ position = ____spriteLines[2 * i].transform.localPosition;
+ ____spriteLines[2 * i].transform.localPosition = new Vector3(0, position.y, position.z);
+ ____spriteLines[2 * i].color = Color.white;
+
+ ____spriteLines[2 * i + 1].sprite = sprite;
+ position = ____spriteLines[2 * i + 1].transform.localPosition;
+ ____spriteLines[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z);
+ ____spriteLines[2 * i + 1].color = Color.white;
+ }
+ }
+ }
+ else
+ {
+ for (var i = 0; i < 11; i++)
+ {
+ sprite = customSlideFan[0, i];
+ if (sprite != null)
+ {
+ ____spriteLines[2 * i].sprite = sprite;
+ position = ____spriteLines[2 * i].transform.localPosition;
+ ____spriteLines[2 * i].transform.localPosition = new Vector3(0, position.y, position.z);
+ ____spriteLines[2 * i].color = Color.white;
+
+ ____spriteLines[2 * i + 1].sprite = sprite;
+ position = ____spriteLines[2 * i + 1].transform.localPosition;
+ ____spriteLines[2 * i + 1].transform.localPosition = new Vector3(0, position.y, position.z);
+ ____spriteLines[2 * i + 1].color = Color.white;
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/AquaMai/Main.cs b/AquaMai/Main.cs
index 7d8619b9..97910f59 100644
--- a/AquaMai/Main.cs
+++ b/AquaMai/Main.cs
@@ -4,8 +4,10 @@ using System.IO;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
+using AquaMai.CustomSkin;
using AquaMai.Fix;
using AquaMai.Helpers;
+using AquaMai.RenderTweak;
using AquaMai.Resources;
using AquaMai.Utils;
using AquaMai.UX;
@@ -164,6 +166,13 @@ namespace AquaMai
Patch(typeof(RunCommandOnEvents));
// Utils
Patch(typeof(JudgeAdjust));
+
+ // My Patches
+ Patch(typeof(CustomNoteSkin));
+ Patch(typeof(SlideAutoPlayTweak));
+ Patch(typeof(TrackStartProcessTweak));
+ Patch(typeof(SlideJudgeTweak));
+ Patch(typeof(FixConnSlide));
# if DEBUG
Patch(typeof(LogNetworkErrors));
# endif