Skip to content
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
161 changes: 161 additions & 0 deletions AquaMai.Mods/Fancy/RsOverride.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
using System;
using System.Collections.Generic;
using System.IO;
using AquaMai.Config.Attributes;
using AquaMai.Core.Helpers;
using HarmonyLib;
using MelonLoader;
using UnityEngine;
using UnityEngine.UI;

namespace AquaMai.Mods.Fancy;

[ConfigSection(
name: "【危险功能】力大砖飞",
en: "Full-scene background replacement. Static injection.",
zh: "适用于便捷魔改的自定义全场景图片。警告:可能对游戏造成未知性能影响,可能与其他模组冲突?")]
public class CustomSkinsPlusStatic
{
[ConfigEntry(name: "资源目录")]
private static string skinsDir = "LocalAssets/Skins";

private static readonly Dictionary<string, Sprite> SpritePool = new Dictionary<string, Sprite>();
private static readonly Dictionary<string, Texture2D> TexturePool = new Dictionary<string, Texture2D>();

public static void OnBeforePatch()
{
string resolvedPath = "";
try { resolvedPath = FileSystem.ResolvePath(skinsDir); }
catch { resolvedPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, skinsDir); }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

使用 try-catch 来处理路径解析逻辑会让代码意图变得不清晰,并且是一种反模式(anti-pattern)。对于游戏模组,使用 AppDomain.CurrentDomain.BaseDirectory 通常比 Environment.CurrentDirectory 更可靠,因为后者可能会在运行时被改变。建议简化此处的路径解析逻辑,直接使用更可靠的基准目录。

        resolvedPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, skinsDir);


if (!Directory.Exists(resolvedPath)) {
Directory.CreateDirectory(resolvedPath);
return;
}

foreach (var file in Directory.GetFiles(resolvedPath, "*.png"))
{
try {
byte[] data = File.ReadAllBytes(file);
var tex = new Texture2D(1, 1, TextureFormat.RGBA32, false);
if (tex.LoadImage(data)) {
var name = Path.GetFileNameWithoutExtension(file).ToLower();
var sprite = Sprite.Create(tex, new Rect(0f, 0f, tex.width, tex.height), new Vector2(0.5f, 0.5f));
sprite.name = name;
tex.name = name;

SpritePool[name] = sprite;
TexturePool[name] = tex;
}
} catch { }

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

catch 块为空,会静默地忽略所有加载图片过程中的错误(例如,文件损坏或格式不正确)。这会使调试变得非常困难,因为用户可能不知道为什么他们的自定义皮肤没有生效。建议在 catch 块中捕获异常并添加日志记录,以便追踪问题。

            } catch (Exception e) { MelonLogger.Error($"Failed to load custom skin '{Path.GetFileName(file)}': {e.Message}"); }

}
}


// 渲染底层


[HarmonyPatch(typeof(Graphic), "Rebuild")]
[HarmonyPrefix]
private static void OnGraphicRebuild(Graphic __instance)
{
if (__instance == null) return;

// 处理 Image 组件
if (__instance is Image img && img.sprite != null)
{
if (SpritePool.TryGetValue(img.sprite.name.ToLower(), out var customSprite))
{
if (img.sprite != customSprite)
{
img.sprite = customSprite;
// 强制恢复
img.canvasRenderer.SetColor(Color.white);
}
}
}
// 处理RawImage
else if (__instance is RawImage rImg && rImg.texture != null)
{
if (TexturePool.TryGetValue(rImg.texture.name.ToLower(), out var customTex))
{
if (rImg.texture != customTex)
{
rImg.texture = customTex;
rImg.canvasRenderer.SetColor(Color.white);
}
}
}
}


//全量暴力同步


[HarmonyPatch(typeof(CanvasScaler), "OnEnable")]
[HarmonyPostfix]
private static void OnCanvasEnable(CanvasScaler __instance)
{
// 立即扫描
var images = __instance.GetComponentsInChildren<Image>(true);
foreach (var img in images)
{
if (img.sprite != null && SpritePool.TryGetValue(img.sprite.name.ToLower(), out var s))
img.sprite = s;
Comment on lines +102 to +103

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

OnGraphicRebuild 方法中,替换图片后会调用 img.canvasRenderer.SetColor(Color.white) 来重置颜色,以避免意外的颜色叠加。但在 OnCanvasEnable 方法中缺少了这个逻辑。为了保持行为一致性并避免潜在的视觉问题,建议在这里也添加颜色重置的逻辑。同时,为了代码可读性和可维护性,建议为 if 语句使用花括号 {}

            if (img.sprite != null && SpritePool.TryGetValue(img.sprite.name.ToLower(), out var s))
            {
                img.sprite = s;
                img.canvasRenderer.SetColor(Color.white);
            }

}

var rImages = __instance.GetComponentsInChildren<RawImage>(true);
foreach (var rImg in rImages)
{
if (rImg.texture != null && TexturePool.TryGetValue(rImg.texture.name.ToLower(), out var t))
rImg.texture = t;
Comment on lines +109 to +110

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

同样地,为了与 OnGraphicRebuild 的行为保持一致,建议在替换 RawImagetexture 后,也重置其 canvasRenderer 的颜色为 Color.white。同时,为了代码可读性和可维护性,建议为 if 语句使用花括号 {}

            if (rImg.texture != null && TexturePool.TryGetValue(rImg.texture.name.ToLower(), out var t))
            {
                rImg.texture = t;
                rImg.canvasRenderer.SetColor(Color.white);
            }

}
}
}
/*
这是一大坨屎山,原理上和 CustomSkinsPlus 差不多?
后续补:功能上差不多,原理上大概完全不一样了。
这个东西可能有点危险,毕竟是静态劫持所有图片赋值操作,理论上会影响性能
不过好处是可以彻底更换大概几乎可能的所有图片资源
Powered By AkiACG Team
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣠⣤⣄⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠻⢿⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣠⣤⣤⣤⣴⣶⣶⣦⣤⣤⣤⣤⣀⣀⠹⣿⡇⣀⣀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⣶⣶⣦⣤⣄⡀⢀⡤⠶⠿⠿⠟⢛⣛⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⢋⣼⣿⣿⣿⣿⣿⣿⣿⣶⡦⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⣁⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣌⡙⠲⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⢟⣭⣶⣶⣶⣮⣝⢿⣿⣿⣿⣧⠀⣠⣶⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠻⢿⣿⣿⣿⣿⣿⣿⣷⣌⠻⣷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⡸⠟⣛⣛⢿⣿⣿⣧⢻⣿⣿⣿⣇⢻⣿⣿⣿⡿⢋⣸⣿⣿⣿⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣶⣿⣿⣿⣿⣿⣿⣿⣿⣧⡘⢿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣾⣿⣿⡇⣻⣿⣿⢸⣿⣿⣿⣿⢸⣿⣿⠟⣠⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⢻⣿⣿⣿⣿⣿⣿⣷⡈⢿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⢩⣾⠟⣫⣵⣿⣿⣿⣿⣿⢸⡿⢃⣼⣿⣿⣿⡟⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡈⢿⣿⣿⣿⣿⣿⣿⣷⡈⢿⣿⣿⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣿⣦⠭⣭⣿⣿⣿⣿⣿⣿⡟⡼⢡⣾⣿⣿⣿⡟⡴⠀⣿⣿⣿⣿⣿⣿⠉⣿⣿⣿⠉⣿⣿⣿⣿⣿⣿⣿⣷⠘⣿⣿⡿⢿⣿⣿⣿⣧⠘⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣇⠾⢆⣽⣿⣿⣿⣿⠟⡴⢡⣿⣿⣿⣿⡟⣼⡇⢸⣿⣿⣿⣿⣿⣿⢰⣿⣿⣿⢠⢻⣿⣿⣿⣿⣿⡿⢟⣃⡭⠶⢚⣡⣿⣿⣿⣿⡆⢻⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⠙⠿⣿⣿⣿⣿⣿⣿⣿⡿⢛⣋⣵⡾⢠⣿⣿⣿⣿⡿⣰⣿⠀⣿⣿⣿⣿⣿⣿⣿⢸⣿⣿⡇⣿⡜⣿⣿⣿⣿⣿⣧⣭⣴⡀⢟⣋⣭⠙⣿⣿⣿⣷⠘⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⢩⣥⣶⣬⣍⣼⣿⣿⠃⣾⣿⣿⢛⣿⢣⣿⣿⢠⣿⣿⣿⣿⣿⣿⣿⢸⣿⣿⡇⣿⡧⠿⠿⣿⣿⣿⣿⡏⠵⠚⢋⣥⣶⣾⣿⣿⣿⣿⡄⣿⣿⣿⣿⣿⠙⢿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⡟⢰⣉⣭⠄⣤⣆⣾⣿⣷⢠⣿⣿⣿⣿⣿⣿⣿⠘⣿⣿⢹⣿⣿⡔⣶⣶⣤⣬⣉⡙⠻⢧⢸⣿⣿⣿⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⡈⠀⠑⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⠃⣾⣿⣏⣴⡿⣼⣿⣿⣿⢸⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣸⣿⣿⣷⢻⣿⣿⣿⣿⣿⣿⣶⠘⣿⣿⣿⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⠀⠛⠛⠚⠘⠓⠿⠿⠿⣯⢈⡛⢹⣿⣿⣿⣿⣿⣧⢿⡏⣿⢿⣿⣿⣯⢿⡿⣿⣿⣿⣿⣿⠀⣿⣿⣿⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡏⢸⣿⣿⣿⣿⣟⠀⠀⣠⣄⣀⡤⣀⣀⣀⣀⡀⠉⠈⣿⣿⣿⣿⣿⣿⡸⣧⣿⡿⠉⠉⠉⠈⠁⠈⠉⠉⠙⠛⠀⣿⣿⣿⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠠⠘⣿⣿⣿⣿⣿⠀⢠⣿⣿⣸⢱⣿⣿⣿⣿⣿⣧⠸⣎⢿⣿⣿⣿⣿⣧⢹⣿⣷⡖⣴⣶⣶⣶⣶⣦⢰⣄⢠⠀⣿⣿⣿⣿⣿⣿⣿⠁⣿⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⡿⠀⣀⠹⣿⣿⣿⣿⡄⢸⣿⣿⡏⣿⣿⣿⣿⣿⣿⣿⡄⢻⣷⡹⢿⣿⡿⣿⣞⣿⣿⣼⣿⣿⣿⣿⣿⣿⡦⣝⠲⢘⣿⣿⣿⣿⣿⣿⠏⠀⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠀⠀⠀⣿⣦⠙⣿⣿⣿⣷⠘⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⢷⣷⣿⣿⣷⣽⣓⣐⣭⣬⡿⣿⣿⣿⣿⣿⣿⣿⣇⣿⣷⢸⣿⣿⡟⢻⣿⠏⡴⢠⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠿⣷⣌⠻⡟⠻⣇⢻⣿⣧⢻⣿⣿⣿⣿⣿⡟⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣹⣿⡇⣾⣿⠟⠀⡼⢃⣾⠇⣸⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⢠⡙⢿⣷⣤⡄⣬⣑⣙⣿⣷⣝⡻⠿⢿⣫⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣮⡻⢿⣿⡿⢟⣵⣿⡟⣸⠟⣡⠎⢀⣴⣿⠋⣼⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢸⡇⣦⣉⡛⠳⠘⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣭⣭⣭⣭⣭⣍⣻⣿⣿⣿⣷⡶⣾⣿⣿⠏⣈⠅⣼⡿⠖⣁⠘⣡⣾⣿⠛⢿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠓⢿⣿⠀⠱⣌⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣽⣿⣿⡿⣿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣵⣿⣩⣬⡴⢂⣾⣿⣿⣿⡿⠁⠀⢸⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠈⠿⣦⣌⡙⠻⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⢋⡡⢴⣿⣿⣿⡿⠋⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠈⠻⢿⣿⣶⣶⣬⣍⠙⠛⣛⣋⣉⠹⡏⠉⣉⣉⣛⣛⣛⣛⡛⠉⢥⣤⣶⠿⠋⢂⣾⣿⠟⠋⠀⠀⠀⠀⠀⢸⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠇⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⠿⠅⠈⢿⣿⣿⠏⣼⣷⡀⣿⣿⣿⣿⣿⣿⣿⣇⠘⠋⠁⠀⠀⠚⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⠿⢦⡍⢱⡇⣿⣿⣿⡿⠿⠟⠛⢋⣠⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣒⡖⠶⣤⠇⣼⠇⣎⣉⣤⣴⣶⣾⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⢿⣿⣿⣶⣤⣅⠙⡂⠿⠿⣿⣿⣿⣿⣿⣿⣿⡏⢹⠃⡤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢸⡇⢸⣿⣿⣿⣿⣿⠀⣧⢰⣶⣤⣤⣉⣙⣿⣿⣿⡇⡈⢰⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⡇⢸⣿⣿⣿⣿⣿⢸⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣞⡇⠀⢸⣿⣿⣿⣿⣿⢸⡇⢸⣿⣿⣿⣿⣿⣿⣿⣿⠀⢀⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣷⠀⢸⣿⣿⣿⣿⣿⢸⡇⢸⣿⣿⣿⣿⣿⣿⣿⣿⡆⢸⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⡆⠘⣿⣿⣿⣿⣟⢸⡇⢼⣿⣿⣿⣿⣿⣿⣿⣿⣇⠸⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⠀⣿⣿⣿⣿⣿⢸⡇⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⡄⢹⣿⣿⣿⣿⢸⡇⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⢻⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
*/