Skip to content
Merged
Changes from all 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
160 changes: 160 additions & 0 deletions AquaMai.Mods/Fancy/RsOverride.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
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: "[Dangerous] Full-scene texture / sprite replacement. Static injection.",
zh: "【危险功能】适用于便捷魔改的自定义全场景图片。警告:可能对游戏造成未知性能影响,可能与其他模组冲突?")]
public class CustomSkinsPlusStatic
{
[ConfigEntry(name: "资源目录")]
private static string skinsDir = "LocalAssets/ResourcesOverride";

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 = "";
resolvedPath = FileSystem.ResolvePath(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 (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
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣠⣤⣄⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠻⢿⣿⣿⣷⣦⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠻⣿⣿⣿⣿⣦⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⣿⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⣠⣤⣤⣤⣴⣶⣶⣦⣤⣤⣤⣤⣀⣀⠹⣿⡇⣀⣀⣀⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣤⣶⣶⣦⣤⣄⡀⢀⡤⠶⠿⠿⠟⢛⣛⠿⠿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣇⢋⣼⣿⣿⣿⣿⣿⣿⣿⣶⡦⢤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⢀⣠⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⠀⠀⠀⠀⣁⣤⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣦⣌⡙⠲⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⣰⣿⣿⣿⢟⣭⣶⣶⣶⣮⣝⢿⣿⣿⣿⣧⠀⣠⣶⣿⣿⣿⣿⣿⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠻⢿⣿⣿⣿⣿⣿⣿⣷⣌⠻⣷⣦⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⡸⠟⣛⣛⢿⣿⣿⣧⢻⣿⣿⣿⣇⢻⣿⣿⣿⡿⢋⣸⣿⣿⣿⣤⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣷⣶⣿⣿⣿⣿⣿⣿⣿⣿⣧⡘⢿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣾⣿⣿⡇⣻⣿⣿⢸⣿⣿⣿⣿⢸⣿⣿⠟⣠⣿⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡟⢻⣿⣿⣿⣿⣿⣿⣷⡈⢿⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⢩⣾⠟⣫⣵⣿⣿⣿⣿⣿⢸⡿⢃⣼⣿⣿⣿⡟⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡈⢿⣿⣿⣿⣿⣿⣿⣷⡈⢿⣿⣿⣿⣧⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠈⢿⣿⣿⣿⣿⣿⣿⣿⣦⠭⣭⣿⣿⣿⣿⣿⣿⡟⡼⢡⣾⣿⣿⣿⡟⡴⠀⣿⣿⣿⣿⣿⣿⠉⣿⣿⣿⠉⣿⣿⣿⣿⣿⣿⣿⣷⠘⣿⣿⡿⢿⣿⣿⣿⣧⠘⣿⣿⣿⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠈⠻⣿⣿⣿⣿⣿⣿⣇⠾⢆⣽⣿⣿⣿⣿⠟⡴⢡⣿⣿⣿⣿⡟⣼⡇⢸⣿⣿⣿⣿⣿⣿⢰⣿⣿⣿⢠⢻⣿⣿⣿⣿⣿⡿⢟⣃⡭⠶⢚⣡⣿⣿⣿⣿⡆⢻⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠈⠙⠿⣿⣿⣿⣿⣿⣿⣿⡿⢛⣋⣵⡾⢠⣿⣿⣿⣿⡿⣰⣿⠀⣿⣿⣿⣿⣿⣿⣿⢸⣿⣿⡇⣿⡜⣿⣿⣿⣿⣿⣧⣭⣴⡀⢟⣋⣭⠙⣿⣿⣿⣷⠘⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⢩⣥⣶⣬⣍⣼⣿⣿⠃⣾⣿⣿⢛⣿⢣⣿⣿⢠⣿⣿⣿⣿⣿⣿⣿⢸⣿⣿⡇⣿⡧⠿⠿⣿⣿⣿⣿⡏⠵⠚⢋⣥⣶⣾⣿⣿⣿⣿⡄⣿⣿⣿⣿⣿⠙⢿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⣿⣿⣿⣿⣿⡟⢰⣉⣭⠄⣤⣆⣾⣿⣷⢠⣿⣿⣿⣿⣿⣿⣿⠘⣿⣿⢹⣿⣿⡔⣶⣶⣤⣬⣉⡙⠻⢧⢸⣿⣿⣿⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⡈⠀⠑⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⠃⣾⣿⣏⣴⡿⣼⣿⣿⣿⢸⣿⣿⣿⣿⣿⣿⣿⡇⣿⣿⣸⣿⣿⣷⢻⣿⣿⣿⣿⣿⣿⣶⠘⣿⣿⣿⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⠀⠛⠛⠚⠘⠓⠿⠿⠿⣯⢈⡛⢹⣿⣿⣿⣿⣿⣧⢿⡏⣿⢿⣿⣿⣯⢿⡿⣿⣿⣿⣿⣿⠀⣿⣿⣿⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡏⢸⣿⣿⣿⣿⣟⠀⠀⣠⣄⣀⡤⣀⣀⣀⣀⡀⠉⠈⣿⣿⣿⣿⣿⣿⡸⣧⣿⡿⠉⠉⠉⠈⠁⠈⠉⠉⠙⠛⠀⣿⣿⣿⣿⣿⣿⣿⡇⢸⣿⣿⣿⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠠⠘⣿⣿⣿⣿⣿⠀⢠⣿⣿⣸⢱⣿⣿⣿⣿⣿⣧⠸⣎⢿⣿⣿⣿⣿⣧⢹⣿⣷⡖⣴⣶⣶⣶⣶⣦⢰⣄⢠⠀⣿⣿⣿⣿⣿⣿⣿⠁⣿⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⡿⠀⣀⠹⣿⣿⣿⣿⡄⢸⣿⣿⡏⣿⣿⣿⣿⣿⣿⣿⡄⢻⣷⡹⢿⣿⡿⣿⣞⣿⣿⣼⣿⣿⣿⣿⣿⣿⡦⣝⠲⢘⣿⣿⣿⣿⣿⣿⠏⠀⣿⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠀⠀⠀⣿⣦⠙⣿⣿⣿⣷⠘⣿⣿⡿⣿⣿⣿⣿⣿⣿⣿⢷⣷⣿⣿⣷⣽⣓⣐⣭⣬⡿⣿⣿⣿⣿⣿⣿⣿⣇⣿⣷⢸⣿⣿⡟⢻⣿⠏⡴⢠⣿⣿⣿⣿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠿⣷⣌⠻⡟⠻⣇⢻⣿⣧⢻⣿⣿⣿⣿⣿⡟⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣹⣿⡇⣾⣿⠟⠀⡼⢃⣾⠇⣸⣿⣿⣿⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢹⢠⡙⢿⣷⣤⡄⣬⣑⣙⣿⣷⣝⡻⠿⢿⣫⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣮⡻⢿⣿⡿⢟⣵⣿⡟⣸⠟⣡⠎⢀⣴⣿⠋⣼⣿⣿⣿⣿⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢸⡇⣦⣉⡛⠳⠘⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣭⣭⣭⣭⣭⣍⣻⣿⣿⣿⣷⡶⣾⣿⣿⠏⣈⠅⣼⡿⠖⣁⠘⣡⣾⣿⠛⢿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠓⢿⣿⠀⠱⣌⠛⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣽⣿⣿⡿⣿⣷⣿⣿⣿⣿⣿⣿⣿⣿⣵⣿⣩⣬⡴⢂⣾⣿⣿⣿⡿⠁⠀⢸⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠈⠿⣦⣌⡙⠻⠿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠟⢋⡡⢴⣿⣿⣿⡿⠋⠀⠀⠀⢸⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⠀⠀⠀⠀⠈⠻⢿⣿⣶⣶⣬⣍⠙⠛⣛⣋⣉⠹⡏⠉⣉⣉⣛⣛⣛⣛⡛⠉⢥⣤⣶⠿⠋⢂⣾⣿⠟⠋⠀⠀⠀⠀⠀⢸⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⠇⠀⠀⠀⠀⠀⠀⠀⠈⠙⠻⠿⠅⠈⢿⣿⣿⠏⣼⣷⡀⣿⣿⣿⣿⣿⣿⣿⣇⠘⠋⠁⠀⠀⠚⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠻⠿⢦⡍⢱⡇⣿⣿⣿⡿⠿⠟⠛⢋⣠⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣒⡖⠶⣤⠇⣼⠇⣎⣉⣤⣴⣶⣾⣿⣿⣿⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡾⢿⣿⣿⣶⣤⣅⠙⡂⠿⠿⣿⣿⣿⣿⣿⣿⣿⡏⢹⠃⡤⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢸⡇⢸⣿⣿⣿⣿⣿⠀⣧⢰⣶⣤⣤⣉⣙⣿⣿⣿⡇⡈⢰⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠀⡇⢸⣿⣿⣿⣿⣿⢸⣿⢸⣿⣿⣿⣿⣿⣿⣿⣿⠀⠀⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣞⡇⠀⢸⣿⣿⣿⣿⣿⢸⡇⢸⣿⣿⣿⣿⣿⣿⣿⣿⠀⢀⣿⣿⣿⣿⣿⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣷⠀⢸⣿⣿⣿⣿⣿⢸⡇⢸⣿⣿⣿⣿⣿⣿⣿⣿⡆⢸⣿⣿⣿⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣿⣿⡆⠘⣿⣿⣿⣿⣟⢸⡇⢼⣿⣿⣿⣿⣿⣿⣿⣿⣇⠸⣿⣿⣿⣿⣿⣿⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⠀⣿⣿⣿⣿⣿⢸⡇⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⠀⣿⣿⣿⣿⣿⣿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⣿⣿⣿⡄⢹⣿⣿⣿⣿⢸⡇⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⡄⢻⣿⣿⣿⣿⣿⣿⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
*/