Files
blockout/BlockOutReplica/Assets/Editor/OriginalVisualRecovery.cs
T
2026-10-20 13:21:04 +08:00

137 lines
8.7 KiB
C#

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Text.RegularExpressions;
using UnityEditor;
using UnityEngine;
namespace BlockOutReplica.Editor
{
public static class OriginalVisualRecovery
{
private const string Destination = "Assets/Resources/OriginalRecovered";
[MenuItem("Block Out/Capture Current Game View")]
public static void CaptureCurrentGameView()
{
if (!EditorApplication.isPlaying) return;
Directory.CreateDirectory("Docs/iterations");
ScreenCapture.CaptureScreenshot("Docs/iterations/2026-10-19-visual-collision.png");
}
[MenuItem("Block Out/Validate Collision Feedback (Play Mode)")]
public static void ValidateCollisionFeedback()
{
var game = UnityEngine.Object.FindObjectOfType<BlockOutReplica.Gameplay.BlockOutGameController>();
if (!EditorApplication.isPlaying || game == null)
{
Debug.LogError("Enter Play Mode before validating collisions.");
return;
}
game.ReviewCollisionFeedback();
}
[MenuItem("Block Out/Recover Original Visual Layers")]
public static void Recover() => RecoverLayers(true);
[MenuItem("Block Out/Recover Original UI And Typography")]
public static void RecoverUiOnly() => RecoverLayers(false);
private static void RecoverLayers(bool includeBoosters)
{
string source = Path.GetFullPath(Path.Combine(Application.dataPath,
"../../reverse/assetripper_export/unity_project/ExportedProject/Assets"));
Directory.CreateDirectory(Destination);
var textures = new Dictionary<string, string>();
foreach (string meta in Directory.GetFiles(Path.Combine(source, "Texture2D"), "*.png.meta"))
{
Match guid = Regex.Match(File.ReadAllText(meta), @"(?m)^guid: (\w+)");
if (guid.Success) textures[guid.Groups[1].Value] = meta.Substring(0, meta.Length - 5);
}
var names = new HashSet<string>(BlockOutReplica.Presentation.OriginalAssetCatalog.RequiredSpriteNames());
string[] layers = {
"Ingame_Header_Button_Default", "ingame_booster_timefreeze2",
"ConvexLeftTop", "ConvexRightTop", "ConvexLeftBottom", "ConvexRightBottom",
"LinearX-Up", "LinearX-Down_0", "BorderLeft", "BorderRight", "BorderTop",
"BorderUpLeftCorner", "BorderUpRightCorner", "BorderDownLeftCorner", "BorderDownRightCorner"
};
names.UnionWith(layers);
foreach (string existing in Directory.GetFiles("Assets/Resources/OriginalUI/Sprites", "*.asset"))
names.Add(Path.GetFileNameWithoutExtension(existing));
names.UnionWith(new[] {"Popup_BG_Default_0", "Popup_BG_Default_Bottom", "Pause_Btn_Resume", "Pause_Btn_Quit",
"Pause_Toggle_BG", "Pause_Toggle_Btn_On", "Pause_Toggle_Btn_Off", "Pause_Icon_Sounds", "Pause_Icon_Musics",
"Pause_Icon_Haptics", "Settings_Exit_Btn", "Settings_Exit_Icon", "Home_Settings_Button", "Home_Settings_Icon", "Home_Button_Red",
"Fail_Popup_Title_Default", "Fail_Popup_Coins", "MoreLives_Icon_Heart", "common_timefreeze2", "common_rocket",
"Profile_Avatar_00", "Profile_Frame_Purple_Big", "Profile_Avatar_Glass_Effect", "Home_ProfilePic_Ref", "heart_limiless-Multiple",
"icon_notime", "icon_coin_bttn", "Ingame_Booster_Counter_Purple", "Ingame_Icon_Coin",
"PrelevelPopup_Lock_Icon", "Shop Small", "Shop Icon", "Shop Icon_Big", "Profile_Frame_Blue_Big", "Profile_Frame_Silver_Big 1",
"Endgame_Popup_Left_Default", "Endgame_Popup_Right_Default", "Endgame_Popup_Inside_Default",
"Endgame_Btn_Quit", "Endgame_Btn_Stroke_Quit", "Endgame_Coin_BG_Defaut", "Endgame_Coin_Icon",
"broken_heart", "Fail_Popup_Coins_Counter_Default", "Fail_Popup_X_Icon", "Fail_Popup_Btn", "Fail_Popup_Btn_Stroke"});
foreach (string sprite in Directory.GetFiles(Path.Combine(source, "Sprite"), "*.asset"))
{
string name = Path.GetFileNameWithoutExtension(sprite);
if (name.StartsWith("Journey_", StringComparison.Ordinal) || name.StartsWith("Nav_Bar_", StringComparison.Ordinal) ||
name.StartsWith("Home_", StringComparison.Ordinal) || name.StartsWith("Prelevel_", StringComparison.Ordinal) ||
name.StartsWith("Pause_", StringComparison.Ordinal) || name.StartsWith("Settings_Exit_", StringComparison.Ordinal) ||
name.StartsWith("Nav_Bar_Icon_Home", StringComparison.Ordinal) || name == "freeze_front" ||
Regex.IsMatch(name, @"^poster[123](?:_thumbnail)?$") ||
name == "Ingame_Freeze_Time_Bar" || name == "Ingame_Freeze_Time_Bar_bg") names.Add(name);
}
foreach (string name in names)
{
string yaml = File.ReadAllText(Path.Combine(source, "Sprite", name + ".asset"));
string guid = Regex.Match(yaml, @"texture: \{fileID: \d+, guid: (\w+)").Groups[1].Value;
string sourceTexture = textures[guid];
string texturePath = Destination + "/" + Path.GetFileName(sourceTexture);
if (!File.Exists(texturePath)) File.Copy(sourceTexture, texturePath);
AssetDatabase.ImportAsset(texturePath, ImportAssetOptions.ForceSynchronousImport);
var importer = (TextureImporter)AssetImporter.GetAtPath(texturePath);
if (importer.textureCompression != TextureImporterCompression.Uncompressed ||
importer.mipmapEnabled || importer.maxTextureSize < 8192 || importer.npotScale != TextureImporterNPOTScale.None)
{
importer.textureType = TextureImporterType.Default;
importer.textureCompression = TextureImporterCompression.Uncompressed;
importer.mipmapEnabled = false;
importer.alphaIsTransparency = true;
importer.maxTextureSize = 8192;
importer.npotScale = TextureImporterNPOTScale.None;
importer.SaveAndReimport();
}
var texture = AssetDatabase.LoadAssetAtPath<Texture2D>(texturePath);
Match rectMatch = Regex.Match(yaml,
@"m_Rect:\s+serializedVersion: \d+\s+x: ([\d.]+)\s+y: ([\d.]+)\s+width: ([\d.]+)\s+height: ([\d.]+)");
var rect = new Rect(Number(rectMatch, 1), Number(rectMatch, 2),
Number(rectMatch, 3), Number(rectMatch, 4));
Match border = Regex.Match(yaml, @"m_Border: \{x: ([\d.]+), y: ([\d.]+), z: ([\d.]+), w: ([\d.]+)\}");
Vector4 sliceBorder = border.Success ? new Vector4(Number(border, 1), Number(border, 2), Number(border, 3), Number(border, 4)) : Vector4.zero;
Match pivotMatch = Regex.Match(yaml, @"m_Pivot: \{x: ([\d.]+), y: ([\d.]+)\}");
Vector2 pivot = pivotMatch.Success ? new Vector2(Number(pivotMatch, 1), Number(pivotMatch, 2)) : Vector2.one * .5f;
Sprite recovered = Sprite.Create(texture, rect, pivot, 100f, 0,
SpriteMeshType.FullRect, sliceBorder);
recovered.name = name;
string assetPath = Destination + "/" + name + ".asset";
Sprite existing = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
if (existing == null) AssetDatabase.CreateAsset(recovered, assetPath);
else { EditorUtility.CopySerialized(recovered, existing); UnityEngine.Object.DestroyImmediate(recovered); }
}
AssetDatabase.SaveAssets();
const string audioDestination = "Assets/Resources/OriginalInteractions/Audio";
Directory.CreateDirectory(audioDestination);
foreach (string name in new[] {"gameplay-music", "home-music2", "ui-click-v1", "win_screen", "Timer_Warning", "Timer_Freeze", "Rocket_Launch", "Rocket_Land", "UFO"})
{
string destination = audioDestination + "/" + name + ".ogg";
if (!File.Exists(destination)) File.Copy(Path.Combine(source, "AudioClip", name + ".ogg"), destination);
}
AssetDatabase.Refresh();
OriginalTypographyRecovery.Recover();
if (includeBoosters) OriginalBoosterRecovery.Recover();
Debug.Log("BLOCKOUT_VISUAL_LAYERS_RECOVERED: " + names.Count);
}
private static float Number(Match match, int index) =>
float.Parse(match.Groups[index].Value, CultureInfo.InvariantCulture);
}
}