mirror of
https://github.com/MewoLab/AquaDX.git
synced 2026-02-15 09:07:28 +08:00
[RF] AquaMai configuration refactor (#82)
更新了配置文件格式,原有的配置文件将被自动无缝迁移,详情请见新的配置文件中的注释(例外:`SlideJudgeTweak` 不再默认启用) 旧配置文件将被重命名备份,如果更新到此版本遇到 Bug 请联系我们 Updated configuration file schema. The old config file will be migrated automatically and seamlessly. See the comments in the new configuration file for details. (Except for `SlideJudgeTweak` is no longer enabled by default) Your old configuration file will be renamed as a backup. If you encounter any bug with this version, please contact us.
This commit is contained in:
146
AquaMai/AquaMai.Mods/GameSystem/Assets/Fonts.cs
Normal file
146
AquaMai/AquaMai.Mods/GameSystem/Assets/Fonts.cs
Normal file
@@ -0,0 +1,146 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Core.Helpers;
|
||||
using HarmonyLib;
|
||||
using MelonLoader;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TextCore.LowLevel;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem.Assets;
|
||||
|
||||
[ConfigSection(
|
||||
en: "Use custom font(s) as fallback or fully replace the original game font.",
|
||||
zh: "使用自定义字体作为回退(解决中文字形缺失问题),或完全替换游戏原字体")]
|
||||
public class Fonts
|
||||
{
|
||||
[ConfigEntry(
|
||||
en: """
|
||||
Font path(s).
|
||||
Use semicolon to separate multiple paths for a fallback chain.
|
||||
Microsoft YaHei Bold by default.
|
||||
""",
|
||||
zh: """
|
||||
字体路径
|
||||
使用分号分隔多个路径以构成 Fallback 链
|
||||
默认为微软雅黑 Bold
|
||||
""")]
|
||||
private static readonly string paths = "%SYSTEMROOT%/Fonts/msyhbd.ttc";
|
||||
|
||||
[ConfigEntry(
|
||||
en: "Add custom font(s) as fallback, use original game font when possible.",
|
||||
zh: "将自定义字体作为游戏原字体的回退,尽可能使用游戏原字体")]
|
||||
private static readonly bool addAsFallback = true;
|
||||
|
||||
private static List<TMP_FontAsset> fontAssets = [];
|
||||
private static readonly List<TMP_FontAsset> processedFonts = [];
|
||||
|
||||
private static TMP_FontAsset replacementFontAsset;
|
||||
private static List<TMP_FontAsset> fallbackFontAssets = [];
|
||||
|
||||
public static void OnBeforePatch()
|
||||
{
|
||||
var paths = Fonts.paths
|
||||
.Split(';')
|
||||
.Where(p => !string.IsNullOrWhiteSpace(p))
|
||||
.Select(FileSystem.ResolvePath);
|
||||
var fonts = paths
|
||||
.Select(p =>
|
||||
{
|
||||
var font = new Font(p);
|
||||
if (font == null)
|
||||
{
|
||||
MelonLogger.Warning($"[Fonts] Font not found: {p}");
|
||||
}
|
||||
return font;
|
||||
})
|
||||
.Where(f => f != null);
|
||||
fontAssets = fonts
|
||||
.Select(f => TMP_FontAsset.CreateFontAsset(f, 90, 9, GlyphRenderMode.SDFAA, 8192, 8192))
|
||||
.ToList();
|
||||
|
||||
if (fontAssets.Count == 0)
|
||||
{
|
||||
MelonLogger.Warning("[Fonts] No font loaded.");
|
||||
}
|
||||
else if (addAsFallback)
|
||||
{
|
||||
fallbackFontAssets = fontAssets;
|
||||
}
|
||||
else
|
||||
{
|
||||
replacementFontAsset = fontAssets[0];
|
||||
fallbackFontAssets = fontAssets.Skip(1).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(TextMeshProUGUI), "Awake")]
|
||||
[HarmonyPostfix]
|
||||
public static void PostAwake(TextMeshProUGUI __instance)
|
||||
{
|
||||
if (fontAssets.Count == 0) return;
|
||||
if (processedFonts.Contains(__instance.font)) return;
|
||||
|
||||
if (replacementFontAsset != null)
|
||||
{
|
||||
ProcessReplacement(__instance);
|
||||
}
|
||||
if (fallbackFontAssets.Count > 0)
|
||||
{
|
||||
ProcessFallback(__instance);
|
||||
}
|
||||
|
||||
processedFonts.Add(__instance.font);
|
||||
}
|
||||
|
||||
private static void ProcessReplacement(TextMeshProUGUI __instance)
|
||||
{
|
||||
# if DEBUG
|
||||
MelonLogger.Msg($"{__instance.font.name} {__instance.text}");
|
||||
# endif
|
||||
|
||||
var materialOrigin = __instance.fontMaterial;
|
||||
var materialSharedOrigin = __instance.fontSharedMaterial;
|
||||
__instance.font = replacementFontAsset;
|
||||
|
||||
# if DEBUG
|
||||
MelonLogger.Msg($"shaderKeywords {materialOrigin.shaderKeywords.Join()} {__instance.fontMaterial.shaderKeywords.Join()}");
|
||||
# endif
|
||||
// __instance.fontSharedMaterial = materialSharedOrigin;
|
||||
|
||||
// 这样之后该有描边的地方整个字后面都是阴影,它不知道哪里是边
|
||||
// materialOrigin.mainTexture = __instance.fontMaterial.mainTexture;
|
||||
// materialOrigin.mainTextureOffset = __instance.fontMaterial.mainTextureOffset;
|
||||
// materialOrigin.mainTextureScale = __instance.fontMaterial.mainTextureScale;
|
||||
// __instance.fontMaterial.CopyPropertiesFromMaterial(materialOrigin);
|
||||
|
||||
// 这样了之后有描边了,但是描边很细
|
||||
// __instance.fontMaterial.shader = materialOrigin.shader;
|
||||
foreach (var keyword in materialOrigin.shaderKeywords)
|
||||
{
|
||||
__instance.fontMaterial.EnableKeyword(keyword);
|
||||
}
|
||||
// __instance.fontMaterial.globalIlluminationFlags = materialOrigin.globalIlluminationFlags;
|
||||
|
||||
// 原来是 underlay,但是复制这三个属性之后就又变成整个字后面都是阴影了
|
||||
// __instance.fontMaterial.SetFloat(ShaderUtilities.ID_UnderlayOffsetY, materialOrigin.GetFloat(ShaderUtilities.ID_UnderlayOffsetY));
|
||||
// __instance.fontMaterial.SetFloat(ShaderUtilities.ID_UnderlayOffsetX, materialOrigin.GetFloat(ShaderUtilities.ID_UnderlayOffsetX));
|
||||
// __instance.fontMaterial.SetFloat(ShaderUtilities.ID_UnderlayDilate, materialOrigin.GetFloat(ShaderUtilities.ID_UnderlayDilate));
|
||||
|
||||
// if(materialOrigin.shaderKeywords.Contains(ShaderUtilities.Keyword_Underlay))
|
||||
// {
|
||||
// __instance.fontMaterial.EnableKeyword(ShaderUtilities.Keyword_Glow);
|
||||
// __instance.fontMaterial.SetFloat(ShaderUtilities.ID_GlowOuter, .5f);
|
||||
// // __instance.fontMaterial.SetFloat(ShaderUtilities.ID_UnderlayOffsetX, materialOrigin.GetFloat(ShaderUtilities.ID_UnderlayOffsetX));
|
||||
// }
|
||||
}
|
||||
|
||||
private static void ProcessFallback(TextMeshProUGUI __instance)
|
||||
{
|
||||
foreach (var fontAsset in fallbackFontAssets)
|
||||
{
|
||||
__instance.font.fallbackFontAssetTable.Add(fontAsset);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using Manager;
|
||||
using Util;
|
||||
using AquaMai.Config.Attributes;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem.Assets;
|
||||
|
||||
[ConfigSection(
|
||||
en: "Load all existing \".ab\" image resources regardless of the AssetBundleImages manifest.",
|
||||
zh: """
|
||||
加载所有存在的 .ab 图片资源(无视 AssetBundleImages.manifest)
|
||||
导入了删除曲包之类的话,应该需要开启这个
|
||||
""")]
|
||||
public class LoadAssetBundleWithoutManifest
|
||||
{
|
||||
private static HashSet<string> abFiles = new HashSet<string>();
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(OptionDataManager), "CheckAssetBundle")]
|
||||
public static void PostCheckAssetBundle(ref Safe.ReadonlySortedDictionary<string, string> abs)
|
||||
{
|
||||
foreach (var ab in abs)
|
||||
{
|
||||
abFiles.Add(ab.Key);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(AssetBundleManifest), "GetAllAssetBundles")]
|
||||
public static bool PreGetAllAssetBundles(AssetBundleManifest __instance, ref string[] __result)
|
||||
{
|
||||
__result = abFiles.ToArray();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
577
AquaMai/AquaMai.Mods/GameSystem/Assets/LoadLocalImages.cs
Normal file
577
AquaMai/AquaMai.Mods/GameSystem/Assets/LoadLocalImages.cs
Normal file
@@ -0,0 +1,577 @@
|
||||
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 AquaMai.Config.Attributes;
|
||||
using AquaMai.Core.Helpers;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem.Assets;
|
||||
|
||||
[ConfigSection(
|
||||
en: "Load asset images from the configured directory (for self-made charts).",
|
||||
zh: "从指定目录下加载资源图片(自制谱用)")]
|
||||
public class LoadLocalImages
|
||||
{
|
||||
[ConfigEntry]
|
||||
private static readonly string localAssetsDir = "LocalAssets";
|
||||
|
||||
private static readonly string[] imageExts = [".jpg", ".png", ".jpeg"];
|
||||
private static readonly Dictionary<string, string> jacketPaths = [];
|
||||
private static readonly Dictionary<string, string> framePaths = [];
|
||||
private static readonly Dictionary<string, string> platePaths = [];
|
||||
private static readonly Dictionary<string, string> framemaskPaths = [];
|
||||
private static readonly Dictionary<string, string> framepatternPaths = [];
|
||||
private static readonly Dictionary<string, string> iconPaths = [];
|
||||
private static readonly Dictionary<string, string> charaPaths = [];
|
||||
private static readonly Dictionary<string, string> partnerPaths = [];
|
||||
//private static readonly Dictionary<string, string> navicharaPaths = [];
|
||||
private static readonly Dictionary<string, string> tabTitlePaths = [];
|
||||
private static readonly Dictionary<string, string> localAssetsContents = [];
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(DataManager), "LoadMusicBase")]
|
||||
public static void LoadMusicPostfix(List<string> ____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, @"AssetBundleImages\frame")))
|
||||
foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\frame")))
|
||||
{
|
||||
if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue;
|
||||
var idStr = Path.GetFileName(file).Substring("ui_frame_".Length, 6);
|
||||
framePaths[idStr] = file;
|
||||
}
|
||||
|
||||
if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\nameplate")))
|
||||
foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\nameplate")))
|
||||
{
|
||||
if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue;
|
||||
var idStr = Path.GetFileName(file).Substring("ui_plate_".Length, 6);
|
||||
platePaths[idStr] = file;
|
||||
}
|
||||
|
||||
if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\framemask")))
|
||||
foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\framemask")))
|
||||
{
|
||||
if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue;
|
||||
var idStr = Path.GetFileName(file).Substring("ui_framemask_".Length, 6);
|
||||
framemaskPaths[idStr] = file;
|
||||
}
|
||||
|
||||
if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\framepattern")))
|
||||
foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\framepattern")))
|
||||
{
|
||||
if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue;
|
||||
var idStr = Path.GetFileName(file).Substring("ui_framepattern_".Length, 6);
|
||||
framepatternPaths[idStr] = file;
|
||||
}
|
||||
|
||||
if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\icon")))
|
||||
foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\icon")))
|
||||
{
|
||||
if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue;
|
||||
var idStr = Path.GetFileName(file).Substring("ui_icon_".Length, 6);
|
||||
iconPaths[idStr] = file;
|
||||
}
|
||||
|
||||
if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\chara")))
|
||||
foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\chara")))
|
||||
{
|
||||
if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue;
|
||||
var idStr = Path.GetFileName(file).Substring("ui_chara_".Length, 6);
|
||||
charaPaths[idStr] = file;
|
||||
}
|
||||
|
||||
if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\partner")))
|
||||
foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\partner")))
|
||||
{
|
||||
if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue;
|
||||
var idStr = Path.GetFileName(file).Substring("ui_Partner_".Length, 6);
|
||||
partnerPaths[idStr] = file;
|
||||
}
|
||||
//if (Directory.Exists(Path.Combine(aDir, @"AssetBundleImages\navichara\sprite\parts\ui_navichara_21")))
|
||||
// foreach (var file in Directory.GetFiles(Path.Combine(aDir, @"AssetBundleImages\navichara\sprite\parts\ui_navichara_", charaid)))
|
||||
//{
|
||||
// if (!imageExts.Contains(Path.GetExtension(file).ToLowerInvariant())) continue;
|
||||
//var idStr = Path.GetFileName(file).Substring("ui_navichara_".Length, 6);
|
||||
// navicharaPaths[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($"[LoadLocalImages] Loaded {jacketPaths.Count} Jacket, {platePaths.Count} NamePlate, {framePaths.Count} Frame, {framemaskPaths.Count} FrameMask, {framepatternPaths.Count} FramePattern, {iconPaths.Count} Icon, {charaPaths.Count} Chara, {partnerPaths.Count} PartnerLogo, {tabTitlePaths.Count} Tab Titles from AssetBundleImages.");
|
||||
|
||||
var resolvedDir = FileSystem.ResolvePath(localAssetsDir);
|
||||
if (Directory.Exists(resolvedDir))
|
||||
foreach (var laFile in Directory.EnumerateFiles(resolvedDir))
|
||||
{
|
||||
if (!imageExts.Contains(Path.GetExtension(laFile).ToLowerInvariant())) continue;
|
||||
localAssetsContents[Path.GetFileNameWithoutExtension(laFile).ToLowerInvariant()] = laFile;
|
||||
}
|
||||
|
||||
MelonLogger.Msg($"[LoadLocalImages] 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, TextureFormat.RGBA32, false);
|
||||
texture.LoadImage(File.ReadAllBytes(path));
|
||||
return texture;
|
||||
}
|
||||
|
||||
public static Texture2D GetJacketTexture2D(int id)
|
||||
{
|
||||
return GetJacketTexture2D($"{id:000000}");
|
||||
}
|
||||
|
||||
private static string GetFramePath(string id)
|
||||
{
|
||||
return framePaths.GetValueOrDefault(id);
|
||||
}
|
||||
|
||||
public static Texture2D GetFrameTexture2D(string id)
|
||||
{
|
||||
var path = GetFramePath(id);
|
||||
if (path == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
|
||||
texture.LoadImage(File.ReadAllBytes(path));
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static string GetPlatePath(string id)
|
||||
{
|
||||
return platePaths.GetValueOrDefault(id);
|
||||
}
|
||||
|
||||
public static Texture2D GetPlateTexture2D(string id)
|
||||
{
|
||||
var path = GetPlatePath(id);
|
||||
if (path == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
|
||||
texture.LoadImage(File.ReadAllBytes(path));
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static string GetFrameMaskPath(string id)
|
||||
{
|
||||
return framemaskPaths.GetValueOrDefault(id);
|
||||
}
|
||||
|
||||
public static Texture2D GetFrameMaskTexture2D(string id)
|
||||
{
|
||||
var path = GetFrameMaskPath(id);
|
||||
if (path == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
|
||||
texture.LoadImage(File.ReadAllBytes(path));
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static string GetFramePatternPath(string id)
|
||||
{
|
||||
return framepatternPaths.GetValueOrDefault(id);
|
||||
}
|
||||
|
||||
public static Texture2D GetFramePatternTexture2D(string id)
|
||||
{
|
||||
var path = GetFramePatternPath(id);
|
||||
if (path == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
|
||||
texture.LoadImage(File.ReadAllBytes(path));
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static string GetIconPath(string id)
|
||||
{
|
||||
return iconPaths.GetValueOrDefault(id);
|
||||
}
|
||||
|
||||
public static Texture2D GetIconTexture2D(string id)
|
||||
{
|
||||
var path = GetIconPath(id);
|
||||
if (path == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
|
||||
texture.LoadImage(File.ReadAllBytes(path));
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static string GetCharaPath(string id)
|
||||
{
|
||||
return charaPaths.GetValueOrDefault(id);
|
||||
}
|
||||
|
||||
public static Texture2D GetCharaTexture2D(string id)
|
||||
{
|
||||
var path = GetCharaPath(id);
|
||||
if (path == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
|
||||
texture.LoadImage(File.ReadAllBytes(path));
|
||||
return texture;
|
||||
}
|
||||
|
||||
private static string GetPartnerPath(string id)
|
||||
{
|
||||
return partnerPaths.GetValueOrDefault(id);
|
||||
}
|
||||
|
||||
public static Texture2D GetPartnerTexture2D(string id)
|
||||
{
|
||||
var path = GetPartnerPath(id);
|
||||
if (path == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var texture = new Texture2D(1, 1, TextureFormat.RGBA32, false);
|
||||
texture.LoadImage(File.ReadAllBytes(path));
|
||||
return texture;
|
||||
}
|
||||
|
||||
/*
|
||||
[HarmonyPatch]
|
||||
public static class TabTitleLoader
|
||||
{
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
// Fxxk unity
|
||||
// game load tab title by call Resources.Load<Sprite> directly
|
||||
// patching Resources.Load<Sprite> 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<int, Sprite> ____genreSprite, Dictionary<int, Sprite> ____versionSprite)
|
||||
{
|
||||
var genres = Singleton<DataManager>.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, TextureFormat.RGBA32, false);
|
||||
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<DataManager>.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, TextureFormat.RGBA32, false);
|
||||
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<MethodBase> 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<Texture2D>($"Jacket/UI_Jacket_{id}.png");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class FrameLoader
|
||||
{
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
var AM = typeof(AssetManager);
|
||||
return [AM.GetMethod("GetFrameThumbTexture2D", [typeof(string)]), AM.GetMethod("GetFrameTexture2D", [typeof(string)])];
|
||||
}
|
||||
|
||||
public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance)
|
||||
{
|
||||
var matches = Regex.Matches(filename, @"UI_Frame_(\d+)(_s)?\.png");
|
||||
if (matches.Count < 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var id = matches[0].Groups[1].Value;
|
||||
|
||||
var texture = GetFrameTexture2D(id);
|
||||
__result = texture ?? __instance.LoadAsset<Texture2D>($"Frame/UI_Frame_{id}.png");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class PlateLoader
|
||||
{
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
var AM = typeof(AssetManager);
|
||||
return [AM.GetMethod("GetPlateTexture2D", [typeof(string)])];
|
||||
}
|
||||
|
||||
public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance)
|
||||
{
|
||||
var matches = Regex.Matches(filename, @"UI_Plate_(\d+)\.png");
|
||||
if (matches.Count < 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var id = matches[0].Groups[1].Value;
|
||||
|
||||
var texture = GetPlateTexture2D(id);
|
||||
__result = texture ?? __instance.LoadAsset<Texture2D>($"NamePlate/UI_Plate_{id}.png");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class FrameMaskLoader
|
||||
{
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
var AM = typeof(AssetManager);
|
||||
return [AM.GetMethod("GetFrameMaskTexture2D", [typeof(string)])];
|
||||
}
|
||||
|
||||
public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance)
|
||||
{
|
||||
var matches = Regex.Matches(filename, @"UI_FrameMask_(\d+)\.png");
|
||||
if (matches.Count < 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var id = matches[0].Groups[1].Value;
|
||||
|
||||
var texture = GetFrameMaskTexture2D(id);
|
||||
__result = texture ?? __instance.LoadAsset<Texture2D>($"FrameMask/UI_FrameMask_{id}.png");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class FramePatternLoader
|
||||
{
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
var AM = typeof(AssetManager);
|
||||
return [AM.GetMethod("GetFramePatternTexture2D", [typeof(string)])];
|
||||
}
|
||||
|
||||
public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance)
|
||||
{
|
||||
var matches = Regex.Matches(filename, @"UI_FramePattern_(\d+)\.png");
|
||||
if (matches.Count < 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var id = matches[0].Groups[1].Value;
|
||||
|
||||
var texture = GetFramePatternTexture2D(id);
|
||||
__result = texture ?? __instance.LoadAsset<Texture2D>($"FramePattern/UI_FramePattern_{id}.png");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Private | Instance
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(AssetManager), "GetIconTexture2D", typeof(string))]
|
||||
public static bool IconLoader(string filename, ref Texture2D __result, AssetManager __instance)
|
||||
{
|
||||
var matches = Regex.Matches(filename, @"UI_Icon_(\d+)\.png");
|
||||
if (matches.Count < 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var id = matches[0].Groups[1].Value;
|
||||
|
||||
var texture = GetIconTexture2D(id);
|
||||
__result = texture ?? __instance.LoadAsset<Texture2D>($"Icon/UI_Icon_{id}.png");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class CharaLoader
|
||||
{
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
var AM = typeof(AssetManager);
|
||||
return [AM.GetMethod("GetCharacterTexture2D", [typeof(string)])];
|
||||
}
|
||||
|
||||
public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance)
|
||||
{
|
||||
var matches = Regex.Matches(filename, @"UI_Chara_(\d+)\.png");
|
||||
if (matches.Count < 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var id = matches[0].Groups[1].Value;
|
||||
|
||||
var texture = GetCharaTexture2D(id);
|
||||
__result = texture ?? __instance.LoadAsset<Texture2D>($"Chara/UI_Chara_{id}.png");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
public static class PartnerLoader
|
||||
{
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
var AM = typeof(AssetManager);
|
||||
return [AM.GetMethod("GetPartnerTexture2D", [typeof(string)])];
|
||||
}
|
||||
|
||||
public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance)
|
||||
{
|
||||
var matches = Regex.Matches(filename, @"UI_Partner_(\d+)\.png");
|
||||
if (matches.Count < 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var id = matches[0].Groups[1].Value;
|
||||
|
||||
var texture = GetPartnerTexture2D(id);
|
||||
__result = texture ?? __instance.LoadAsset<Texture2D>($"Partner/UI_Partner_{id}.png");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/*
|
||||
[HarmonyPatch]
|
||||
public static class FrameLoader
|
||||
{
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
var AM = typeof(AssetManager);
|
||||
return [AM.GetMethod("GetFrameThumbTexture2D", [typeof(string)]), AM.GetMethod("GetFrameTexture2D", [typeof(string)])];
|
||||
}
|
||||
|
||||
public static bool Prefix(string filename, ref Texture2D __result, AssetManager __instance)
|
||||
{
|
||||
var matches = Regex.Matches(filename, @"UI_Frame_(\d+)\.png");
|
||||
if (matches.Count < 1)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var id = matches[0].Groups[1].Value;
|
||||
|
||||
var texture = GetFrameTexture2D(id);
|
||||
__result = texture ?? __instance.LoadAsset<Texture2D>($"Frame/UI_Frame_{id}.png");
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
using System.Linq;
|
||||
using AquaMai.Config.Attributes;
|
||||
using HarmonyLib;
|
||||
using MAI2.Util;
|
||||
using Manager;
|
||||
using MelonLoader;
|
||||
using Monitor.Game;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem.Assets;
|
||||
|
||||
[ConfigSection(
|
||||
en: """
|
||||
Use the png jacket above as MV if no .dat found in the movie folder.
|
||||
Use together with `LoadLocalImages`.
|
||||
""",
|
||||
zh: """
|
||||
如果 movie 文件夹中没有 dat 格式的 MV 的话,就用歌曲的封面做背景,而不是显示迪拉熊的笑脸
|
||||
请和 `LoadLocalImages` 一起用
|
||||
""")]
|
||||
public class UseJacketAsDummyMovie
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(GameCtrl), "IsReady")]
|
||||
public static void LoadLocalBgaAwake(GameObject ____movieMaskObj)
|
||||
{
|
||||
var music = Singleton<DataManager>.Instance.GetMusic(GameManager.SelectMusicID[0]);
|
||||
if (music is null) return;
|
||||
|
||||
var moviePath = Singleton<OptionDataManager>.Instance.GetMovieDataPath($"{music.movieName.id:000000}") + ".dat";
|
||||
if (!moviePath.Contains("dummy")) return;
|
||||
|
||||
var jacket = LoadLocalImages.GetJacketTexture2D(music.movieName.id);
|
||||
if (jacket is null)
|
||||
{
|
||||
MelonLogger.Msg("No jacket found for music " + music);
|
||||
return;
|
||||
}
|
||||
|
||||
var components = ____movieMaskObj.GetComponentsInChildren<Component>(false);
|
||||
var movies = components.Where(it => it.name == "Movie");
|
||||
|
||||
foreach (var movie in movies)
|
||||
{
|
||||
// If I create a new RawImage component, the jacket will be not be displayed
|
||||
// I think it will be difficult to make it work with RawImage
|
||||
// So I change the material that plays video to default sprite material
|
||||
// The original player is actually a sprite renderer and plays video with a custom material
|
||||
var sprite = movie.GetComponent<SpriteRenderer>();
|
||||
sprite.sprite = Sprite.Create(jacket, new Rect(0, 0, jacket.width, jacket.height), new Vector2(0.5f, 0.5f));
|
||||
sprite.material = new Material(Shader.Find("Sprites/Default"));
|
||||
}
|
||||
}
|
||||
}
|
||||
142
AquaMai/AquaMai.Mods/GameSystem/CustomCameraId.cs
Normal file
142
AquaMai/AquaMai.Mods/GameSystem/CustomCameraId.cs
Normal file
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using HarmonyLib;
|
||||
using Manager;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
using AquaMai.Config.Attributes;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem;
|
||||
|
||||
[ConfigSection(
|
||||
en: """
|
||||
Use custom CameraId rather than the default ones.
|
||||
If enabled, you can customize the game to use the specified camera.
|
||||
""",
|
||||
zh: """
|
||||
使用自定义的摄像头 ID 而不是默认的
|
||||
启用后可以指定游戏使用的摄像头
|
||||
""")]
|
||||
public class CustomCameraId
|
||||
{
|
||||
[ConfigEntry(
|
||||
en: "Print the camera list to the log when starting, can be used as a basis for modification.",
|
||||
zh: "启动时打印摄像头列表到日志中,可以作为修改的依据")]
|
||||
public static bool printCameraList;
|
||||
|
||||
[ConfigEntry(
|
||||
en: "DX Pass 1P.",
|
||||
zh: "DX Pass 1P")]
|
||||
public static int leftQrCamera;
|
||||
|
||||
[ConfigEntry(
|
||||
en: "DX Pass 2P.",
|
||||
zh: "DX Pass 2P")]
|
||||
public static int rightQrCamera;
|
||||
|
||||
[ConfigEntry(
|
||||
en: "Player Camera.",
|
||||
zh: "玩家摄像头")]
|
||||
public static int photoCamera;
|
||||
|
||||
[ConfigEntry(
|
||||
en: "WeChat QRCode Camera.",
|
||||
zh: "二维码扫描摄像头")]
|
||||
public static int chimeCamera;
|
||||
|
||||
private static readonly Dictionary<string, string> cameraTypeMap = new()
|
||||
{
|
||||
["LeftQrCamera"] = "QRLeft",
|
||||
["RightQrCamera"] = "QRRight",
|
||||
["PhotoCamera"] = "Photo",
|
||||
["ChimeCamera"] = "Chime",
|
||||
};
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(CameraManager), "CameraInitialize")]
|
||||
public static bool CameraInitialize(CameraManager __instance, ref IEnumerator __result)
|
||||
{
|
||||
__result = CameraInitialize(__instance);
|
||||
return false;
|
||||
}
|
||||
|
||||
private static IEnumerator CameraInitialize(CameraManager __instance)
|
||||
{
|
||||
var textureCache = new WebCamTexture[WebCamTexture.devices.Length];
|
||||
SortedDictionary<CameraManager.CameraTypeEnum, WebCamTexture> webCamTextures = [];
|
||||
foreach (var (configEntry, cameraTypeName) in cameraTypeMap)
|
||||
{
|
||||
int deviceId = Traverse.Create(typeof(CustomCameraId)).Field(configEntry).GetValue<int>();
|
||||
if (deviceId < 0 || deviceId >= WebCamTexture.devices.Length)
|
||||
{
|
||||
MelonLogger.Warning($"[CustomCameraId] Ignoring custom camera {configEntry}: camera ID {deviceId} out of range");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!Enum.TryParse<CameraManager.CameraTypeEnum>(cameraTypeName, out var cameraType))
|
||||
{
|
||||
MelonLogger.Warning($"[CustomCameraId] Ignoring custom camera {configEntry}: camera type {cameraTypeName} not present");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (textureCache[deviceId] != null)
|
||||
{
|
||||
webCamTextures[cameraType] = textureCache[deviceId];
|
||||
}
|
||||
else
|
||||
{
|
||||
var webCamTexture = new WebCamTexture(WebCamTexture.devices[deviceId].name);
|
||||
webCamTextures[cameraType] = webCamTexture;
|
||||
textureCache[deviceId] = webCamTexture;
|
||||
}
|
||||
}
|
||||
|
||||
int textureCount = webCamTextures.Count;
|
||||
__instance.isAvailableCamera = new bool[textureCount];
|
||||
__instance.cameraProcMode = new CameraManager.CameraProcEnum[textureCount];
|
||||
|
||||
int textureIndex = 0;
|
||||
foreach (var (cameraType, webCamTexture) in webCamTextures)
|
||||
{
|
||||
__instance.isAvailableCamera[textureIndex] = true;
|
||||
__instance.cameraProcMode[textureIndex] = CameraManager.CameraProcEnum.Good;
|
||||
CameraManager.DeviceId[(int)cameraType] = textureIndex;
|
||||
textureIndex++;
|
||||
}
|
||||
Traverse.Create(__instance).Field("_webcamtex").SetValue(webCamTextures.Values.ToArray());
|
||||
|
||||
CameraManager.IsReady = true;
|
||||
yield break;
|
||||
}
|
||||
|
||||
public static void OnBeforePatch()
|
||||
{
|
||||
if (!printCameraList)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
WebCamDevice[] devices = WebCamTexture.devices;
|
||||
string cameraList = "Connected Web Cameras:\n";
|
||||
for (int i = 0; i < devices.Length; i++)
|
||||
{
|
||||
WebCamDevice webCamDevice = devices[i];
|
||||
WebCamTexture webCamTexture = new WebCamTexture(webCamDevice.name);
|
||||
webCamTexture.Play();
|
||||
cameraList += "==================================================\n";
|
||||
cameraList += "Name: " + webCamDevice.name + "\n";
|
||||
cameraList += $"ID: {i}\n";
|
||||
cameraList += $"Resolution: {webCamTexture.width} * {webCamTexture.height}\n";
|
||||
cameraList += $"FPS: {webCamTexture.requestedFPS}\n";
|
||||
webCamTexture.Stop();
|
||||
}
|
||||
cameraList += "==================================================";
|
||||
|
||||
foreach (var line in cameraList.Split('\n'))
|
||||
{
|
||||
MelonLogger.Msg($"[CustomCameraId] {line}");
|
||||
}
|
||||
}
|
||||
}
|
||||
65
AquaMai/AquaMai.Mods/GameSystem/DisableTimeout.cs
Normal file
65
AquaMai/AquaMai.Mods/GameSystem/DisableTimeout.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using AquaMai.Config.Attributes;
|
||||
using HarmonyLib;
|
||||
using Manager;
|
||||
using Monitor;
|
||||
using Process;
|
||||
using Process.Entry.State;
|
||||
using Process.ModeSelect;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem;
|
||||
|
||||
[ConfigSection(
|
||||
en: """
|
||||
Disable timers (hidden and set to 65535 seconds).
|
||||
Not recommand to enable when SinglePlayer is off.
|
||||
""",
|
||||
zh: """
|
||||
去除游戏中的倒计时(隐藏并设为 65535 秒)
|
||||
没有开启单人模式时,不建议启用
|
||||
""")]
|
||||
public class DisableTimeout
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(TimerController), "PrepareTimer")]
|
||||
public static void PrePrepareTimer(ref int second)
|
||||
{
|
||||
second = 65535;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(CommonTimer), "SetVisible")]
|
||||
public static void CommonTimerSetVisible(ref bool isVisible)
|
||||
{
|
||||
isVisible = false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(EntryProcess), "DecrementTimerSecond")]
|
||||
public static bool EntryProcessDecrementTimerSecond(ContextEntry ____context)
|
||||
{
|
||||
SoundManager.PlaySE(Mai2.Mai2Cue.Cue.SE_SYS_SKIP, 0);
|
||||
____context.SetState(StateType.DoneEntry);
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(ModeSelectProcess), "UpdateInput")]
|
||||
public static bool ModeSelectProcessUpdateInput(ModeSelectProcess __instance)
|
||||
{
|
||||
if (!InputManager.GetButtonDown(0, InputManager.ButtonSetting.Button05)) return true;
|
||||
__instance.TimeSkipButtonAnim(InputManager.ButtonSetting.Button05);
|
||||
SoundManager.PlaySE(Mai2.Mai2Cue.Cue.SE_SYS_SKIP, 0);
|
||||
Traverse.Create(__instance).Method("TimeUp").GetValue();
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(PhotoEditProcess), "MainMenuUpdate")]
|
||||
public static void PhotoEditProcess(PhotoEditMonitor[] ____monitors, PhotoEditProcess __instance)
|
||||
{
|
||||
if (!InputManager.GetButtonDown(0, InputManager.ButtonSetting.Button04)) return;
|
||||
SoundManager.PlaySE(Mai2.Mai2Cue.Cue.SE_SYS_SKIP, 0);
|
||||
____monitors[0].SetButtonPressed(InputManager.ButtonSetting.Button04);
|
||||
Traverse.Create(__instance).Method("OnTimeUp").GetValue();
|
||||
}
|
||||
}
|
||||
80
AquaMai/AquaMai.Mods/GameSystem/KeyMap.cs
Normal file
80
AquaMai/AquaMai.Mods/GameSystem/KeyMap.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using System.Reflection;
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Config.Types;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem;
|
||||
|
||||
[ConfigSection(
|
||||
en: "These settings will work regardless of whether you have enabled segatools' io4 emulation.",
|
||||
zh: "这里的设置无论你是否启用了 segatools 的 io4 模拟都会工作")]
|
||||
public class KeyMap
|
||||
{
|
||||
[ConfigEntry]
|
||||
public static readonly KeyCodeID Test = (KeyCodeID)115;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Service = (KeyCodeID)5;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button1_1P = (KeyCodeID)67;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button2_1P = (KeyCodeID)49;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button3_1P = (KeyCodeID)48;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button4_1P = (KeyCodeID)47;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button5_1P = (KeyCodeID)68;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button6_1P = (KeyCodeID)70;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button7_1P = (KeyCodeID)45;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button8_1P = (KeyCodeID)61;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Select_1P = (KeyCodeID)25;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button1_2P = (KeyCodeID)80;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button2_2P = (KeyCodeID)81;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button3_2P = (KeyCodeID)78;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button4_2P = (KeyCodeID)75;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button5_2P = (KeyCodeID)74;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button6_2P = (KeyCodeID)73;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button7_2P = (KeyCodeID)76;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Button8_2P = (KeyCodeID)79;
|
||||
|
||||
[ConfigEntry]
|
||||
private static readonly KeyCodeID Select_2P = (KeyCodeID)84;
|
||||
|
||||
[HarmonyPatch(typeof(DB.JvsButtonTableRecord), MethodType.Constructor, typeof(int), typeof(string), typeof(string), typeof(int), typeof(string), typeof(int), typeof(int), typeof(int))]
|
||||
[HarmonyPostfix]
|
||||
public static void JvsButtonTableRecordConstructor(DB.JvsButtonTableRecord __instance, string Name)
|
||||
{
|
||||
var prop = (DB.KeyCodeID)typeof(KeyMap).GetField(Name, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Static).GetValue(null);
|
||||
__instance.SubstituteKey = prop;
|
||||
}
|
||||
}
|
||||
34
AquaMai/AquaMai.Mods/GameSystem/QuickRetry.cs
Normal file
34
AquaMai/AquaMai.Mods/GameSystem/QuickRetry.cs
Normal file
@@ -0,0 +1,34 @@
|
||||
using AquaMai.Core.Attributes;
|
||||
using AquaMai.Config.Attributes;
|
||||
using HarmonyLib;
|
||||
using Manager;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem;
|
||||
|
||||
[ConfigSection(
|
||||
en: "Hold the bottom four buttons (3456) for quick retry (like in Freedom Mode, default non-utage only).",
|
||||
zh: "按住下方四个按钮(3456)快速重开本局游戏(像在 Freedom Mode 中一样,默认仅对非宴谱有效)")]
|
||||
[EnableGameVersion(23000)]
|
||||
public class QuickRetry
|
||||
{
|
||||
[ConfigEntry(
|
||||
en: "Force enable in Utage.",
|
||||
zh: "在宴谱中强制启用")]
|
||||
private static readonly bool enableInUtage = false;
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(Monitor.QuickRetry), "IsQuickRetryEnable")]
|
||||
public static bool OnQuickRetryIsQuickRetryEnable(ref bool __result)
|
||||
{
|
||||
if (enableInUtage)
|
||||
{
|
||||
__result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
var isUtageProperty = Traverse.Create(typeof(GameManager)).Property("IsUtage");
|
||||
__result = !isUtageProperty.PropertyExists() || !isUtageProperty.GetValue<bool>();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
5
AquaMai/AquaMai.Mods/GameSystem/README.md
Normal file
5
AquaMai/AquaMai.Mods/GameSystem/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# GameSystem
|
||||
|
||||
Patches changing the way the game running / behaving which are not possible in the stock game. See also the [GameSettings README](../GameSettings/README.md) for differences.
|
||||
|
||||
Game asset related patches should go to the Assets subcategory (or the Fancy category if they're too fancy).
|
||||
63
AquaMai/AquaMai.Mods/GameSystem/RemoveEncryption.cs
Normal file
63
AquaMai/AquaMai.Mods/GameSystem/RemoveEncryption.cs
Normal file
@@ -0,0 +1,63 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using AquaMai.Config.Attributes;
|
||||
using HarmonyLib;
|
||||
using Net.Packet;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem;
|
||||
|
||||
[ConfigSection(
|
||||
en: """
|
||||
If you are using an unmodified client, requests to the server will be encrypted by default, but requests to the private server should not be encrypted.
|
||||
With this option enabled, the connection will not be encrypted, and the suffix added by different versions of the client to the API names are also removed.
|
||||
Please keep this option enabled normally.
|
||||
""",
|
||||
zh: """
|
||||
如果你在用未经修改的客户端,会默认加密到服务器的连接,而连接私服的时候不应该加密
|
||||
开了这个选项之后就不会加密连接了,同时也会移除不同版本的客户端可能会对 API 接口加的后缀
|
||||
正常情况下,请保持这个选项开启
|
||||
""",
|
||||
defaultOn: true)]
|
||||
public class RemoveEncryption
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(Packet), "Obfuscator", typeof(string))]
|
||||
public static bool PreObfuscator(string srcStr, ref string __result)
|
||||
{
|
||||
__result = srcStr.Replace("MaimaiExp", "").Replace("MaimaiChn", "");
|
||||
return false;
|
||||
}
|
||||
|
||||
[HarmonyPatch]
|
||||
public class EncryptDecrypt
|
||||
{
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
var methods = AccessTools.TypeByName("Net.CipherAES").GetMethods();
|
||||
return
|
||||
[
|
||||
methods.FirstOrDefault(it => it.Name == "Encrypt" && it.IsPublic),
|
||||
methods.FirstOrDefault(it => it.Name == "Decrypt" && it.IsPublic)
|
||||
];
|
||||
}
|
||||
|
||||
public static bool Prefix(object[] __args, ref object __result)
|
||||
{
|
||||
if (__args.Length == 1)
|
||||
{
|
||||
// public static byte[] Encrypt(byte[] data)
|
||||
// public static byte[] Decrypt(byte[] encryptData)
|
||||
__result = __args[0];
|
||||
}
|
||||
else if (__args.Length == 2)
|
||||
{
|
||||
// public static bool Encrypt(byte[] data, out byte[] encryptData)
|
||||
// public static bool Decrypt(byte[] encryptData, out byte[] plainData)
|
||||
__args[1] = __args[0];
|
||||
__result = true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
114
AquaMai/AquaMai.Mods/GameSystem/SinglePlayer.cs
Normal file
114
AquaMai/AquaMai.Mods/GameSystem/SinglePlayer.cs
Normal file
@@ -0,0 +1,114 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Reflection;
|
||||
using AquaMai.Core;
|
||||
using AquaMai.Core.Attributes;
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Mods.Fancy.GamePlay;
|
||||
using HarmonyLib;
|
||||
using MAI2.Util;
|
||||
using Manager;
|
||||
using MelonLoader;
|
||||
using Monitor;
|
||||
using Monitor.Common;
|
||||
using Monitor.Entry;
|
||||
using Monitor.Entry.Parts.Screens;
|
||||
using UnityEngine;
|
||||
using Fx;
|
||||
using Type = System.Type;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem;
|
||||
|
||||
// Hides the 2p (right hand side) UI.
|
||||
// Note: this is not my original work. I simply interpreted the code and rewrote it as a mod.
|
||||
[ConfigSection(
|
||||
en: "Single player: Show 1P only, at the center of the screen.",
|
||||
zh: "单人模式,不显示 2P")]
|
||||
public class SinglePlayer
|
||||
{
|
||||
[HarmonyPatch]
|
||||
public class WhateverInitialize
|
||||
{
|
||||
public static IEnumerable<MethodBase> TargetMethods()
|
||||
{
|
||||
var lateInitialize = AccessTools.Method(typeof(Main.GameMain), "LateInitialize", [typeof(MonoBehaviour), typeof(Transform), typeof(Transform)]);
|
||||
if (lateInitialize is not null) return [lateInitialize];
|
||||
return [AccessTools.Method(typeof(Main.GameMain), "Initialize", [typeof(MonoBehaviour), typeof(Transform), typeof(Transform)])];
|
||||
}
|
||||
|
||||
public static void Prefix(MonoBehaviour gameMainObject, ref Transform left, ref Transform right)
|
||||
{
|
||||
left.transform.position = Vector3.zero;
|
||||
right.localScale = Vector3.zero;
|
||||
GameObject.Find("Mask").transform.position = new Vector3(540f, 0f, 0f);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(MeshButton), "IsPointInPolygon", new Type[] { typeof(Vector2[]), typeof(Vector2) })]
|
||||
public static bool IsPointInPolygon(Vector2[] polygon, ref Vector2 point, MeshButton __instance, ref bool __result)
|
||||
{
|
||||
__result = RectTransformUtility.RectangleContainsScreenPoint(__instance.GetComponent<RectTransform>(), point, Camera.main);
|
||||
return false;
|
||||
}
|
||||
|
||||
[EnableGameVersion(21500, noWarn: true)]
|
||||
public class SkipTimer
|
||||
{
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(EntryMonitor), "DecideEntry")]
|
||||
public static void PostDecideEntry(EntryMonitor __instance)
|
||||
{
|
||||
# if DEBUG
|
||||
MelonLogger.Msg("Confirm Entry");
|
||||
# endif
|
||||
TimeManager.MarkGameStartTime();
|
||||
Singleton<EventManager>.Instance.UpdateEvent();
|
||||
Singleton<ScoreRankingManager>.Instance.UpdateData();
|
||||
__instance.Process.CreateDownloadProcess();
|
||||
__instance.ProcessManager.SendMessage(new Message(ProcessType.CommonProcess, 30001));
|
||||
__instance.ProcessManager.SendMessage(new Message(ProcessType.CommonProcess, 40000, 0, OperationInformationController.InformationType.Hide));
|
||||
__instance.Process.SetNextProcess();
|
||||
}
|
||||
|
||||
// To prevent the "長押受付終了" overlay from appearing
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(WaitPartner), "Open")]
|
||||
public static bool WaitPartnerPreOpen()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[ConfigEntry(
|
||||
en: "Fix hanabi effect under single-player mode (disabled automatically if HideHanabi is enabled).",
|
||||
zh: "修复单人模式下的烟花效果(如果启用了 HideHanabi,则会自动禁用)")]
|
||||
public static bool fixHanabi = true;
|
||||
|
||||
private static bool fixHanabiDisableImplied = false;
|
||||
private static bool FixHanabiEnabled => fixHanabi && !fixHanabiDisableImplied;
|
||||
|
||||
[EnableIf(nameof(FixHanabiEnabled))]
|
||||
[HarmonyPatch(typeof(TapCEffect), "SetUpParticle")]
|
||||
[HarmonyPostfix]
|
||||
public static void PostSetUpParticle(TapCEffect __instance, FX_Mai2_Note_Color ____particleControler)
|
||||
{
|
||||
var entities = ____particleControler.GetComponentsInChildren<ParticleSystemRenderer>(true);
|
||||
foreach (var entity in entities)
|
||||
{
|
||||
entity.maxParticleSize = 1f;
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnBeforePatch()
|
||||
{
|
||||
if (ConfigLoader.Config.GetSectionState(typeof(HideHanabi)).Enabled)
|
||||
{
|
||||
fixHanabiDisableImplied = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void OnAfterPatch()
|
||||
{
|
||||
Core.Helpers.GuiSizes.SinglePlayer = true;
|
||||
}
|
||||
}
|
||||
69
AquaMai/AquaMai.Mods/GameSystem/TestProof.cs
Normal file
69
AquaMai/AquaMai.Mods/GameSystem/TestProof.cs
Normal file
@@ -0,0 +1,69 @@
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Config.Types;
|
||||
using AquaMai.Core;
|
||||
using AquaMai.Core.Attributes;
|
||||
using AquaMai.Core.Helpers;
|
||||
using AquaMai.Mods.UX;
|
||||
using AquaMai.Mods.UX.PracticeMode;
|
||||
using HarmonyLib;
|
||||
using Manager;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem;
|
||||
|
||||
[ConfigSection(
|
||||
en: """
|
||||
When enabled, test button must be long pressed to enter game test mode.
|
||||
When test button is bound to other features, this option is enabled automatically.
|
||||
""",
|
||||
zh: """
|
||||
启用后,测试键必须长按才能进入游戏测试模式
|
||||
当测试键被绑定到其它功能时,此选项自动开启
|
||||
""")]
|
||||
[EnableImplicitlyIf(nameof(ShouldEnableImplicitly))]
|
||||
public class TestProof
|
||||
{
|
||||
public static bool ShouldEnableImplicitly
|
||||
{
|
||||
get
|
||||
{
|
||||
(System.Type section, KeyCodeOrName key)[] featureKeys =
|
||||
[
|
||||
(typeof(OneKeyEntryEnd), OneKeyEntryEnd.key),
|
||||
(typeof(OneKeyRetrySkip), OneKeyRetrySkip.retryKey),
|
||||
(typeof(OneKeyRetrySkip), OneKeyRetrySkip.skipKey),
|
||||
(typeof(HideSelfMadeCharts), HideSelfMadeCharts.key),
|
||||
(typeof(PracticeMode), PracticeMode.key),
|
||||
];
|
||||
var keyMapEnabled = ConfigLoader.Config.GetSectionState(typeof(KeyMap)).Enabled;
|
||||
return featureKeys.Any(it =>
|
||||
// The feature is enabled and...
|
||||
ConfigLoader.Config.GetSectionState(it.section).Enabled &&
|
||||
(
|
||||
// and the key is test, or...
|
||||
it.key == KeyCodeOrName.Test ||
|
||||
// or the key have been mapped to the same key as test.
|
||||
(keyMapEnabled && it.key.ToString() == KeyMap.Test.ToString())));
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(InputManager), "GetSystemInputDown")]
|
||||
public static bool GetSystemInputDown(ref bool __result, InputManager.SystemButtonSetting button, bool[] ___SystemButtonDown)
|
||||
{
|
||||
__result = ___SystemButtonDown[(int)button];
|
||||
if (button != InputManager.SystemButtonSetting.ButtonTest)
|
||||
return false;
|
||||
|
||||
var stackTrace = new StackTrace(); // get call stack
|
||||
var stackFrames = stackTrace.GetFrames(); // get method calls (frames)
|
||||
|
||||
if (stackFrames.Any(it => it.GetMethod().Name == "DMD<Main.GameMainObject::Update>"))
|
||||
{
|
||||
__result = KeyListener.GetKeyDownOrLongPress(KeyCodeOrName.Test, true);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
30
AquaMai/AquaMai.Mods/GameSystem/TouchPanelBaudRate.cs
Normal file
30
AquaMai/AquaMai.Mods/GameSystem/TouchPanelBaudRate.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using AquaMai.Config.Attributes;
|
||||
using HarmonyLib;
|
||||
using IO;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem;
|
||||
|
||||
[ConfigSection(
|
||||
en: """
|
||||
Adjust the baud rate of the touch screen serial port, default value is 9600.
|
||||
Requires hardware support. If you are unsure, don't use it.
|
||||
""",
|
||||
zh: """
|
||||
调整触摸屏串口波特率,默认值 9600
|
||||
需要硬件配合,如果你不清楚你是否可以使用,请不要使用
|
||||
""")]
|
||||
public class TouchPanelBaudRate
|
||||
{
|
||||
[ConfigEntry(
|
||||
en: "Baud rate.",
|
||||
zh: "波特率")]
|
||||
private static readonly int baudRate = 9600;
|
||||
|
||||
[HarmonyPatch(typeof(NewTouchPanel), "Open")]
|
||||
[HarmonyPrefix]
|
||||
private static void OpenPrefix(ref int ___BaudRate)
|
||||
{
|
||||
if (baudRate <= 0) return;
|
||||
___BaudRate = baudRate;
|
||||
}
|
||||
}
|
||||
59
AquaMai/AquaMai.Mods/GameSystem/TouchToButtonInput.cs
Normal file
59
AquaMai/AquaMai.Mods/GameSystem/TouchToButtonInput.cs
Normal file
@@ -0,0 +1,59 @@
|
||||
using AquaMai.Config.Attributes;
|
||||
using HarmonyLib;
|
||||
using Process;
|
||||
using static Manager.InputManager;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem;
|
||||
|
||||
[ConfigSection(
|
||||
en: "Map touch actions to buttons.",
|
||||
zh: "映射触摸操作至实体按键")]
|
||||
public class TouchToButtonInput
|
||||
{
|
||||
private static bool _isPlaying = false;
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(GameProcess), "OnStart")]
|
||||
public static void OnGameProcessStart(GameProcess __instance)
|
||||
{
|
||||
_isPlaying = true;
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(GameProcess), "OnRelease")]
|
||||
public static void OnGameProcessRelease(GameProcess __instance)
|
||||
{
|
||||
_isPlaying = false;
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(Manager.InputManager), "GetButtonDown")]
|
||||
public static void GetButtonDown(ref bool __result, int monitorId, ButtonSetting button)
|
||||
{
|
||||
if (_isPlaying || __result) return;
|
||||
if (button.ToString().StartsWith("Button"))
|
||||
{
|
||||
__result = GetTouchPanelAreaDown(monitorId, (TouchPanelArea)button);
|
||||
}
|
||||
else if (button.ToString().Equals("Select"))
|
||||
{
|
||||
__result = GetTouchPanelAreaLongPush(monitorId, TouchPanelArea.C1, 500L) || GetTouchPanelAreaLongPush(monitorId, TouchPanelArea.C2, 500L);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(Manager.InputManager), "GetButtonPush")]
|
||||
public static void GetButtonPush(ref bool __result, int monitorId, ButtonSetting button)
|
||||
{
|
||||
if (_isPlaying || __result) return;
|
||||
if (button.ToString().StartsWith("Button")) __result = GetTouchPanelAreaPush(monitorId, (TouchPanelArea)button);
|
||||
}
|
||||
|
||||
[HarmonyPostfix]
|
||||
[HarmonyPatch(typeof(Manager.InputManager), "GetButtonLongPush")]
|
||||
public static void GetButtonLongPush(ref bool __result, int monitorId, ButtonSetting button, long msec)
|
||||
{
|
||||
if (_isPlaying || __result) return;
|
||||
if (button.ToString().StartsWith("Button")) __result = GetTouchPanelAreaLongPush(monitorId, (TouchPanelArea)button, msec);
|
||||
}
|
||||
}
|
||||
90
AquaMai/AquaMai.Mods/GameSystem/Unlock.cs
Normal file
90
AquaMai/AquaMai.Mods/GameSystem/Unlock.cs
Normal file
@@ -0,0 +1,90 @@
|
||||
using AquaMai.Config.Attributes;
|
||||
using AquaMai.Core.Attributes;
|
||||
using MAI2System;
|
||||
using Manager;
|
||||
using Manager.MaiStudio;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem;
|
||||
|
||||
[ConfigSection(
|
||||
en: "Unlock normally locked (including normally non-unlockable) game content.",
|
||||
zh: "解锁原本锁定(包括正常途径无法解锁)的游戏内容")]
|
||||
public class Unlock
|
||||
{
|
||||
[ConfigEntry(
|
||||
en: "Unlock maps that are not in this version.",
|
||||
zh: "解锁游戏里所有的区域,包括非当前版本的(并不会帮你跑完)")]
|
||||
private static readonly bool maps = true;
|
||||
|
||||
[EnableIf(nameof(maps))]
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(MapData), "get_OpenEventId")]
|
||||
public static bool get_OpenEventId(ref StringID __result)
|
||||
{
|
||||
// For any map, return the event ID 1 to unlock it
|
||||
var id = new Manager.MaiStudio.Serialize.StringID
|
||||
{
|
||||
id = 1,
|
||||
str = "無期限常時解放"
|
||||
};
|
||||
|
||||
var sid = new StringID();
|
||||
sid.Init(id);
|
||||
|
||||
__result = sid;
|
||||
return false;
|
||||
}
|
||||
|
||||
[ConfigEntry(
|
||||
en: "Unlock normally event-only tickets.",
|
||||
zh: "解锁游戏里所有可能的跑图券")]
|
||||
private static readonly bool tickets = true;
|
||||
|
||||
[EnableIf(nameof(tickets))]
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(TicketData), "get_ticketEvent")]
|
||||
public static bool get_ticketEvent(ref StringID __result)
|
||||
{
|
||||
// For any ticket, return the event ID 1 to unlock it
|
||||
var id = new Manager.MaiStudio.Serialize.StringID
|
||||
{
|
||||
id = 1,
|
||||
str = "無期限常時解放"
|
||||
};
|
||||
|
||||
var sid = new StringID();
|
||||
sid.Init(id);
|
||||
|
||||
__result = sid;
|
||||
return false;
|
||||
}
|
||||
|
||||
[EnableIf(nameof(tickets))]
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(TicketData), "get_maxCount")]
|
||||
public static bool get_maxCount(ref int __result)
|
||||
{
|
||||
// Modify the maxTicketNum to 0
|
||||
// this is because TicketManager.GetTicketData adds the ticket to the list if either
|
||||
// the player owns at least one ticket or the maxTicketNum = 0
|
||||
__result = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
[ConfigEntry(
|
||||
en: "Unlock Utage without the need of DXRating 10000.",
|
||||
zh: "不需要万分也可以进宴会场")]
|
||||
private static readonly bool utage = true;
|
||||
|
||||
[EnableIf(nameof(utage))]
|
||||
[EnableGameVersion(24000)]
|
||||
[HarmonyPrefix]
|
||||
[HarmonyPatch(typeof(GameManager), "CanUnlockUtageTotalJudgement")]
|
||||
public static bool CanUnlockUtageTotalJudgement(out ConstParameter.ResultOfUnlockUtageJudgement result1P, out ConstParameter.ResultOfUnlockUtageJudgement result2P)
|
||||
{
|
||||
result1P = ConstParameter.ResultOfUnlockUtageJudgement.Unlocked;
|
||||
result2P = ConstParameter.ResultOfUnlockUtageJudgement.Unlocked;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
107
AquaMai/AquaMai.Mods/GameSystem/Window.cs
Normal file
107
AquaMai/AquaMai.Mods/GameSystem/Window.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Threading.Tasks;
|
||||
using AquaMai.Config.Attributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace AquaMai.Mods.GameSystem;
|
||||
|
||||
[ConfigSection(
|
||||
en: "Windowed Mode / Window Settings.",
|
||||
zh: "窗口化/窗口设置")]
|
||||
public class Window
|
||||
{
|
||||
[ConfigEntry(
|
||||
en: "Window the game.",
|
||||
zh: "窗口化游戏")]
|
||||
private static readonly bool windowed = false;
|
||||
|
||||
[ConfigEntry(
|
||||
en: """
|
||||
Window width (and height) for windowed mode, rendering resolution for fullscreen mode.
|
||||
If set to 0, windowed mode will remember the user-set size, fullscreen mode will use the current display resolution.
|
||||
""",
|
||||
zh: """
|
||||
宽度(和高度)窗口化时为游戏窗口大小,全屏时为渲染分辨率
|
||||
如果设为 0,窗口化将记住用户设定的大小,全屏时将使用当前显示器分辨率
|
||||
""")]
|
||||
private static readonly int width = 0;
|
||||
|
||||
[ConfigEntry(
|
||||
en: "Height, as above.",
|
||||
zh: "高度,同上")]
|
||||
private static readonly int height = 0;
|
||||
|
||||
private const int GWL_STYLE = -16;
|
||||
private const int WS_WHATEVER = 0x14CF0000;
|
||||
|
||||
private static IntPtr hwnd = IntPtr.Zero;
|
||||
|
||||
public static void OnBeforePatch()
|
||||
{
|
||||
if (windowed)
|
||||
{
|
||||
var alreadyWindowed = Screen.fullScreenMode == FullScreenMode.Windowed;
|
||||
if (width == 0 || height == 0)
|
||||
{
|
||||
Screen.fullScreenMode = FullScreenMode.Windowed;
|
||||
}
|
||||
else
|
||||
{
|
||||
alreadyWindowed = false;
|
||||
Screen.SetResolution(width, height, FullScreenMode.Windowed);
|
||||
}
|
||||
|
||||
hwnd = GetWindowHandle();
|
||||
if (alreadyWindowed)
|
||||
{
|
||||
SetResizeable();
|
||||
}
|
||||
else
|
||||
{
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(3000);
|
||||
// Screen.SetResolution has delay
|
||||
SetResizeable();
|
||||
});
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
var width = Window.width == 0 ? Display.main.systemWidth : Window.width;
|
||||
var height = Window.height == 0 ? Display.main.systemHeight : Window.height;
|
||||
Screen.SetResolution(width, height, FullScreenMode.FullScreenWindow);
|
||||
}
|
||||
}
|
||||
|
||||
public static void SetResizeable()
|
||||
{
|
||||
if (hwnd == IntPtr.Zero) return;
|
||||
SetWindowLongPtr(hwnd, GWL_STYLE, WS_WHATEVER);
|
||||
}
|
||||
|
||||
private delegate bool EnumThreadDelegate(IntPtr hwnd, IntPtr lParam);
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern bool EnumThreadWindows(int dwThreadId, EnumThreadDelegate lpfn, IntPtr lParam);
|
||||
|
||||
[DllImport("Kernel32.dll")]
|
||||
static extern int GetCurrentThreadId();
|
||||
|
||||
static IntPtr GetWindowHandle()
|
||||
{
|
||||
IntPtr returnHwnd = IntPtr.Zero;
|
||||
var threadId = GetCurrentThreadId();
|
||||
EnumThreadWindows(threadId,
|
||||
(hWnd, lParam) =>
|
||||
{
|
||||
if (returnHwnd == IntPtr.Zero) returnHwnd = hWnd;
|
||||
return true;
|
||||
}, IntPtr.Zero);
|
||||
return returnHwnd;
|
||||
}
|
||||
|
||||
[DllImport("user32.dll")]
|
||||
static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, int dwNewLong);
|
||||
}
|
||||
Reference in New Issue
Block a user