Skip to content

BlockRender.decompiled.cs ​

源文件:code/BlockRender.decompiled.cs

csharp
// =============================================================================
// BlockArea 渲染核心 —— 由 APK 内 libil2cpp.so(ARM64) 反汇编还原的等价 C#
//
// 依据:libil2cpp.so + dump.cs(Il2CppDumper 6.7.46),capstone 5.0.7。
// 每个方法标注 VA;泛型实参、静态单例、内置枚举等均由 ELF 重定位/元数据槽反查确定。
//
// ShaderIDs 字段名取自 dump.cs `BlockRender.ShaderIDs`(0x0..0xAC 共 44 个),
// 属性名 = "_" + 字段名,且与 9 个 GLSL 的 uniform 名逐一吻合(三重印证)。
// Graphics.Blit 会自动把 source 作为 _MainTex 绑定,故下面显式 SetTexture
// 出现的 +0x14 是 ComposedRT(_ComposeRT),不是 _MainTex。
// =============================================================================

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public partial class BlockRender : MonoBehaviour
{
    // 材质/相机/RT 字段偏移(dump.cs):
    // 相机 0x20 normal | 0x28 subtract | 0x30 disabledNormal | 0x38 disabledSubtract
    //      0x40 disabledNormalReady | 0x48 disabledSubtractReady | 0x50 touch
    // 材质 0x60 active | 0x68 disabled | 0x70 ready | 0x78 touchEffect
    //      0x80 compose | 0x88 edgeMask | 0x90 glowMask
    // RT   0xB8 sceneColor | 0xC0 normal | 0xC8 subtract | 0xD0 disabledNormal
    //      0xD8 disabledSubtract | 0xE0 disabledNormalReady | 0xE8 disabledSubtractReady
    //      0xF0 composedEnabled | 0xF8 composedDisabled | 0x100 touch | 0x108 effect
    //      0x110 pingA | 0x118 pingB | 0x120 totalRT
    // 其它 0xA0 touchHoverPrefab | 0xA8 mainCamera | 0xB0 cmd | 0x98 fxRenderList
    //      0x128 touchBlockSlots(10) | 0x130 edgeSize | 0x134 glowRadius
    //      0x138 glowWeightFalloff | 0x13C glowPassWeightThreshold
    //      0x140 touchPos | 0x148 touchShineSpeed | 0x14C touchShineLowThreshold
    //      0x150 touchShineBrightness | 0x58 blockShaderVariantCollection

    // 字段名/顺序 = dump.cs BlockRender.ShaderIDs;属性名 "_"+字段名(与 GLSL 一致)
    private static class ShaderIDs
    {
        public static readonly int NormalBlockRT          = Shader.PropertyToID("_NormalBlockRT");          // 0x00
        public static readonly int SubtractBlockRT        = Shader.PropertyToID("_SubtractBlockRT");        // 0x04
        public static readonly int DisabledNormalBlockRT  = Shader.PropertyToID("_DisabledNormalBlockRT");  // 0x08
        public static readonly int DisabledSubtractBlockRT= Shader.PropertyToID("_DisabledSubtractBlockRT");// 0x0C
        public static readonly int SceneColor             = Shader.PropertyToID("_SceneColor");             // 0x10
        public static readonly int ComposedRT             = Shader.PropertyToID("_ComposeRT");              // 0x14
        public static readonly int EffectRT               = Shader.PropertyToID("_EffectRT");               // 0x18
        public static readonly int DilateTexelSize        = Shader.PropertyToID("_DilateTexelSize");        // 0x1C
        public static readonly int PassWeight             = Shader.PropertyToID("_PassWeight");             // 0x20
        public static readonly int GlowFirstPass          = Shader.PropertyToID("_GlowFirstPass");          // 0x24
        public static readonly int TouchHoverRT           = Shader.PropertyToID("_TouchHoverRT");           // 0x28
        public static readonly int TouchPosCount          = Shader.PropertyToID("_TouchPosCount");          // 0x2C
        public static readonly int TouchPos               = Shader.PropertyToID("_TouchPos");               // 0x30
        public static readonly int TouchPosShine          = Shader.PropertyToID("_TouchPosShine");          // 0x34
        public static readonly int TouchPosShineSpeed     = Shader.PropertyToID("_TouchPosShineSpeed");     // 0x38
        public static readonly int TouchPosLowThreshold   = Shader.PropertyToID("_TouchPosLowThreshold");   // 0x3C
        public static readonly int TouchPosBrightness     = Shader.PropertyToID("_TouchPosBrightness");     // 0x40
        public static readonly int ReadyComposeRT         = Shader.PropertyToID("_ReadyComposeRT");         // 0x44
        public static readonly int ShineSpeed             = Shader.PropertyToID("_ShineSpeed");             // 0x48
        public static readonly int ShineBrightness        = Shader.PropertyToID("_ShineBrightness");        // 0x4C
        public static readonly int ShineColor             = Shader.PropertyToID("_ShineColor");             // 0x50
        public static readonly int DisplaceMap            = Shader.PropertyToID("_DisplaceMap");            // 0x54
        public static readonly int DisplaceSpeed          = Shader.PropertyToID("_DisplaceSpeed");          // 0x58
        public static readonly int DisplaceStrength       = Shader.PropertyToID("_DisplaceStrength");       // 0x5C
        public static readonly int DisplaceDirection      = Shader.PropertyToID("_DisplaceDirection");      // 0x60
        public static readonly int TouchDisplaceMap       = Shader.PropertyToID("_TouchDisplaceMap");       // 0x64
        public static readonly int TouchDisplaceSpeed     = Shader.PropertyToID("_TouchDisplaceSpeed");     // 0x68
        public static readonly int TouchDisplaceStrength  = Shader.PropertyToID("_TouchDisplaceStrength");  // 0x6C
        public static readonly int TouchDisplaceDirection = Shader.PropertyToID("_TouchDisplaceDirection"); // 0x70
        public static readonly int GlowColor              = Shader.PropertyToID("_GlowColor");              // 0x74
        public static readonly int TouchGlowColor         = Shader.PropertyToID("_TouchGlowColor");         // 0x78
        public static readonly int BackgroundPixelScale   = Shader.PropertyToID("_BackgroundPixelScale");   // 0x7C
        public static readonly int TouchBackgroundPixelScale = Shader.PropertyToID("_TouchBackgroundPixelScale"); // 0x80
        public static readonly int NoiseMap               = Shader.PropertyToID("_NoiseMap");               // 0x84
        public static readonly int NoiseTint              = Shader.PropertyToID("_NoiseTint");              // 0x88
        public static readonly int NoiseRadius            = Shader.PropertyToID("_NoiseRadius");            // 0x8C
        public static readonly int NoiseSmoothness        = Shader.PropertyToID("_NoiseSmoothness");        // 0x90
        public static readonly int NoiseEvoSpeed          = Shader.PropertyToID("_NoiseEvoSpeed");          // 0x94
        public static readonly int NoiseDirChangeSpeed    = Shader.PropertyToID("_NoiseDirChangeSpeed");    // 0x98
        public static readonly int NoiseDisplaceStrength  = Shader.PropertyToID("_NoiseDisplaceStrength");  // 0x9C
        public static readonly int SDFCellSize            = Shader.PropertyToID("_SDFCellSize");            // 0xA0
        public static readonly int SDFSmoothness          = Shader.PropertyToID("_SDFSmoothness");          // 0xA4
        public static readonly int SDFFalloff             = Shader.PropertyToID("_SDFFalloff");             // 0xA8
        public static readonly int SDFMoveSpeed           = Shader.PropertyToID("_SDFMoveSpeed");           // 0xAC
    }

    // 触摸槽位(BlockRender 内嵌类型;+0x10 fingerId / +0x14 touchedThisFrame / +0x18 behavior)
    private class TouchBlockSlot
    {
        public int fingerId = -1;
        public bool touchedThisFrame;
        public TouchBlockBehavior behavior;
    }

    // ---- VA 0x1D1BC4C -------------------------------------------------------
    private void Start()
    {
        if (mainCamera == null) mainCamera = Camera.main;
        if (mainCamera == null) return;                       // 反汇编 0x1D1C4E8

        if (blockShaderVariantCollection != null) blockShaderVariantCollection.WarmUp();
        mainCamera.forceIntoRenderTexture = true;

        // ---- 13 张 RT(尺寸/格式见注释;除法来源见 render.md)----
        int w = Screen.width, h = Screen.height;
        sceneColorRT              = CreateRenderTexture(w / 6, h / 6);                       // fmt 16
        // fxRenderList 的画布拉满屏(用 mainCamera 的正交尺寸)
        float halfH = mainCamera.orthographicSize;
        float fullW = halfH * 2f * mainCamera.aspect;
        foreach (var canvas in fxRenderList)
        {
            canvas.worldCamera = mainCamera;
            // 反汇编:canvas.gameObject.GetComponent<RectTransform>(),再 set_sizeDelta
            canvas.gameObject.GetComponent<RectTransform>().sizeDelta = new Vector2(fullW, halfH * 2f);
        }
        int mw = w / 8, mh = h / 8;
        normalBlockRT             = CreateRenderTexture(mw, mh, (RenderTextureFormat)16);    // 0xC0
        subtractBlockRT           = CreateRenderTexture(mw, mh, (RenderTextureFormat)16);    // 0xC8
        composedEnabledBlockRT    = CreateRenderTexture(mw, mh, (RenderTextureFormat)16);    // 0xF0
        disabledNormalBlockRT     = CreateRenderTexture(mw, mh, (RenderTextureFormat)25);    // 0xD0
        disabledSubtractBlockRT   = CreateRenderTexture(mw, mh, (RenderTextureFormat)25);    // 0xD8
        disabledNormalReadyBlockRT= CreateRenderTexture(mw, mh, (RenderTextureFormat)25);    // 0xE0
        disabledSubtractReadyBlockRT = CreateRenderTexture(mw, mh, (RenderTextureFormat)25);// 0xE8
        composedDisabledBlockRT   = CreateRenderTexture(mw, mh, (RenderTextureFormat)25);    // 0xF8
        touchBlockRT              = CreateRenderTexture(mw, mh, (RenderTextureFormat)16);    // 0x100
        effectRT    = CreateRenderTexture(w / 4, h / 4, (RenderTextureFormat)25, FilterMode.Bilinear); // 0x108
        pingA       = CreateRenderTexture(w / 4, h / 4, (RenderTextureFormat)25);             // 0x110
        pingB       = CreateRenderTexture(w / 4, h / 4, (RenderTextureFormat)25);             // 0x118

        // ---- 7 台相机 targetTexture(0x20..0x50,各 8 字节)----
        normalBlockCamera.targetTexture = normalBlockRT;
        subtractBlockCamera.targetTexture = subtractBlockRT;
        disabledNormalBlockCamera.targetTexture = disabledNormalBlockRT;
        disabledSubtractBlockCamera.targetTexture = disabledSubtractBlockRT;
        disabledNormalReadyBlockCamera.targetTexture = disabledNormalReadyBlockRT;
        disabledSubtractReadyBlockCamera.targetTexture = disabledSubtractReadyBlockRT;
        touchBlockCamera.targetTexture = touchBlockRT;

        // ---- 材质纹理绑定(nameID 偏移取自码流,已按 ShaderIDs 表还原)----
        blockComposeMaterial.SetTexture(ShaderIDs.NormalBlockRT, normalBlockRT);
        blockComposeMaterial.SetTexture(ShaderIDs.SubtractBlockRT, subtractBlockRT);
        blockComposeMaterial.SetTexture(ShaderIDs.DisabledNormalBlockRT, disabledNormalBlockRT);
        blockComposeMaterial.SetTexture(ShaderIDs.DisabledSubtractBlockRT, disabledSubtractBlockRT);

        activeBlockMaterial.SetTexture(ShaderIDs.EffectRT, effectRT);
        activeBlockMaterial.SetTexture(ShaderIDs.ComposedRT, composedEnabledBlockRT);
        activeBlockMaterial.SetTexture(ShaderIDs.SceneColor, sceneColorRT);
        activeBlockMaterial.SetTexture(ShaderIDs.ReadyComposeRT, composedDisabledBlockRT);
        activeBlockMaterial.SetTexture(ShaderIDs.DisabledNormalBlockRT, disabledNormalReadyBlockRT);
        activeBlockMaterial.SetTexture(ShaderIDs.DisabledSubtractBlockRT, disabledSubtractReadyBlockRT);
        activeBlockMaterial.SetTexture(ShaderIDs.TouchHoverRT, touchBlockRT);

        touchShineSpeed        = activeBlockMaterial.GetFloat(ShaderIDs.TouchPosShineSpeed);
        touchShineLowThreshold = activeBlockMaterial.GetFloat(ShaderIDs.TouchPosLowThreshold);
        touchShineBrightness   = activeBlockMaterial.GetFloat(ShaderIDs.TouchPosBrightness);

        disabledBlockMaterial.SetTexture(ShaderIDs.ComposedRT, composedDisabledBlockRT);

        blockReadyMaterial.SetTexture(ShaderIDs.DisabledNormalBlockRT, disabledNormalReadyBlockRT);
        blockReadyMaterial.SetTexture(ShaderIDs.DisabledSubtractBlockRT, disabledSubtractReadyBlockRT);
        blockReadyMaterial.SetTexture(ShaderIDs.ComposedRT, composedDisabledBlockRT);

        touchEffectMaterial.SetTexture(ShaderIDs.TouchHoverRT, touchBlockRT);
        edgeMaskMaterial.SetTexture(ShaderIDs.ComposedRT, composedEnabledBlockRT);
        glowMaskMaterial.SetTexture(ShaderIDs.ComposedRT, composedEnabledBlockRT);

        CopyReadyTouchParamsToActive();
        UpdateDilateTexelSize();

        // ---- 10 个触摸槽位 ----
        // 反汇编:Instantiate<GameObject>(touchHoverPrefab, transform) →
        //         GetComponent<TouchBlockBehavior>() → Initialize() → new TouchBlockSlot { fingerId=-1, behavior }
        for (int i = 0; i < 10; i++)
        {
            var go = Object.Instantiate<GameObject>(touchHoverPrefab, transform);
            var behavior = go.GetComponent<TouchBlockBehavior>();
            behavior.Initialize();
            touchBlockSlots[i] = new TouchBlockSlot { fingerId = -1, behavior = behavior };
        }
    }

    // ---- VA 0x1D1C6EC -------------------------------------------------------
    // 从 blockReadyMaterial 拷 Shine*、从 touchEffectMaterial 拷 touch 参数到 activeBlockMaterial
    private void CopyReadyTouchParamsToActive()
    {
        if (activeBlockMaterial == null || blockReadyMaterial == null || touchEffectMaterial == null) return;
        var dst = activeBlockMaterial;

        dst.SetFloat(ShaderIDs.ShineSpeed,      blockReadyMaterial.GetFloat(ShaderIDs.ShineSpeed));
        dst.SetFloat(ShaderIDs.ShineBrightness, blockReadyMaterial.GetFloat(ShaderIDs.ShineBrightness));
        dst.SetColor(ShaderIDs.ShineColor,      blockReadyMaterial.GetColor(ShaderIDs.ShineColor));

        CopyTexture(dst, ShaderIDs.TouchDisplaceMap, touchEffectMaterial, ShaderIDs.DisplaceMap);
        dst.SetFloat(ShaderIDs.TouchDisplaceSpeed,     touchEffectMaterial.GetFloat(ShaderIDs.DisplaceSpeed));
        dst.SetFloat(ShaderIDs.TouchDisplaceStrength,  touchEffectMaterial.GetFloat(ShaderIDs.DisplaceStrength));
        dst.SetVector(ShaderIDs.TouchDisplaceDirection, touchEffectMaterial.GetVector(ShaderIDs.DisplaceDirection));
        dst.SetColor(ShaderIDs.TouchGlowColor,         touchEffectMaterial.GetColor(ShaderIDs.GlowColor));
        dst.SetFloat(ShaderIDs.TouchBackgroundPixelScale, touchEffectMaterial.GetFloat(ShaderIDs.BackgroundPixelScale));

        CopyTexture(dst, ShaderIDs.NoiseMap, touchEffectMaterial, ShaderIDs.NoiseMap);
        dst.SetColor(ShaderIDs.NoiseTint, touchEffectMaterial.GetColor(ShaderIDs.NoiseTint));
        dst.SetFloat(ShaderIDs.NoiseRadius,           touchEffectMaterial.GetFloat(ShaderIDs.NoiseRadius));
        dst.SetFloat(ShaderIDs.NoiseSmoothness,       touchEffectMaterial.GetFloat(ShaderIDs.NoiseSmoothness));
        dst.SetFloat(ShaderIDs.NoiseEvoSpeed,         touchEffectMaterial.GetFloat(ShaderIDs.NoiseEvoSpeed));
        dst.SetFloat(ShaderIDs.NoiseDirChangeSpeed,   touchEffectMaterial.GetFloat(ShaderIDs.NoiseDirChangeSpeed));
        dst.SetFloat(ShaderIDs.NoiseDisplaceStrength, touchEffectMaterial.GetFloat(ShaderIDs.NoiseDisplaceStrength));
        dst.SetFloat(ShaderIDs.SDFCellSize,           touchEffectMaterial.GetFloat(ShaderIDs.SDFCellSize));
        dst.SetFloat(ShaderIDs.SDFSmoothness,         touchEffectMaterial.GetFloat(ShaderIDs.SDFSmoothness));
        dst.SetFloat(ShaderIDs.SDFFalloff,            touchEffectMaterial.GetFloat(ShaderIDs.SDFFalloff));
        dst.SetFloat(ShaderIDs.SDFMoveSpeed,          touchEffectMaterial.GetFloat(ShaderIDs.SDFMoveSpeed));
    }

    // ---- VA 0x1D1C5B0 -------------------------------------------------------
    private RenderTexture CreateRenderTexture(int width, int height,
        RenderTextureFormat format = (RenderTextureFormat)16, FilterMode filterMode = FilterMode.Point)
    {
        var rt = new RenderTexture(width, height, 0, format, RenderTextureReadWrite.Default);
        rt.useMipMap = false;
        rt.filterMode = filterMode;
        rt.autoGenerateMips = false;
        rt.Create();
        totalRT.Add(rt);
        return rt;
    }

    // ---- VA 0x1D1D6A0 -------------------------------------------------------
    private static void ClearRenderTexture(RenderTexture rt)
    {
        if (rt == null) return;
        var prev = RenderTexture.active;
        RenderTexture.active = rt;
        GL.Clear(false, true, Color.clear);
        RenderTexture.active = prev;
    }

    // ---- VA 0x1D1D600 / 0x1D1D608 -------------------------------------------
    public void SetTouchPos(List<Vector4> pos) { touchPos = pos; }

    private static void CopyTexture(Material dst, int dstId, Material src, int srcId)
    {
        if (src == null) return;
        dst.SetTexture(dstId, src.GetTexture(srcId));
        dst.SetTextureScale(dstId, src.GetTextureScale(srcId));
        dst.SetTextureOffset(dstId, src.GetTextureOffset(srcId));
    }

    // ---- VA 0x1D1CEBC -------------------------------------------------------
    // 仅是一条尾调用:Update() => UpdateTouchPos()(反汇编首指令即 b 0x1D1CEC0)。
    private void Update() => UpdateTouchPos();

    // ---- VA 0x1D1DB54 -------------------------------------------------------
    // 释放:移除命令缓冲 → 清部分相机的 targetTexture → Release + Clear(totalRT)。
    // 反汇编里**只**清这 4 台:0x20 normal / 0x28 subtract / 0x30 disabledNormal / 0x50 touch;
    // 0x38 disabledSubtract、0x40/0x48 两台 *Ready* **未在此撤销**(不对称,照抄)。
    private void OnDestroy()
    {
        if (mainCamera != null && cmd != null)
            mainCamera.RemoveCommandBuffer(CameraEvent.BeforeImageEffects, cmd);  // 0x12 = 18
        if (cmd != null) cmd.Release();
        cmd = null;

        if (normalBlockCamera != null)         normalBlockCamera.targetTexture = null;          // 0x20
        if (subtractBlockCamera != null)       subtractBlockCamera.targetTexture = null;        // 0x28
        if (disabledNormalBlockCamera != null) disabledNormalBlockCamera.targetTexture = null;  // 0x30
        if (touchBlockCamera != null)          touchBlockCamera.targetTexture = null;           // 0x50
        // 0x38 / 0x40 / 0x48 未清(照抄 APK 行为)。

        if (totalRT != null)
        {
            foreach (var rt in totalRT)
                if (rt != null) rt.Release();
            totalRT.Clear();
        }
    }

    // ---- VA 0x1D1CC04 -------------------------------------------------------
    private void UpdateDilateTexelSize()
    {
        if (effectRT == null || edgeMaskMaterial == null || glowMaskMaterial == null) return;
        var v = new Vector4(1f / effectRT.width, 1f / effectRT.height,
                            effectRT.width, effectRT.height);
        edgeMaskMaterial.SetVector(ShaderIDs.DilateTexelSize, v);
        glowMaskMaterial.SetVector(ShaderIDs.DilateTexelSize, v);
    }

    // ---- VA 0x1D1D750 -------------------------------------------------------
    // .rodata: kEpsFalloff = 0.001f (0xC26530), kEpsSum = 1e-6f (0xC26384)
    private static float GetGlowRingWeight(int passIndex, int glowRadius, float falloff)
    {
        const float kEpsFalloff = 0.001f;
        const float kEpsSum = 1e-6f;
        if (glowRadius < 1) return 0f;
        if (falloff > kEpsFalloff)
        {
            float sum = 0f;
            for (int i = glowRadius; i > 0; i--) sum += Mathf.Pow(i, falloff);
            if (sum > kEpsSum) return Mathf.Pow(glowRadius - passIndex, falloff) / sum;
        }
        return 1f / glowRadius;
    }

    // ---- VA 0x1D1CFD8 -------------------------------------------------------
    private void LateUpdate()
    {
        if (composedEnabledBlockRT == null || composedDisabledBlockRT == null || effectRT == null) return;
        UpdateDilateTexelSize();
        Graphics.Blit(null, composedEnabledBlockRT, blockComposeMaterial, 0);
        RenderEffects(composedEnabledBlockRT, effectRT);
        Graphics.Blit(null, composedDisabledBlockRT, blockComposeMaterial, 1);
        RefreshSceneColorCommands();
    }

    // ---- VA 0x1D1D114 -------------------------------------------------------
    private void RenderEffects(RenderTexture sourceMask, RenderTexture destinationEffect)
    {
        ClearRenderTexture(destinationEffect);
        // 显式绑定的 +0x14 是 _ComposeRT(sourceMask 同时由 Blit 自动绑为 _MainTex)
        edgeMaskMaterial.SetTexture(ShaderIDs.ComposedRT, sourceMask);
        glowMaskMaterial.SetTexture(ShaderIDs.ComposedRT, sourceMask);

        if (edgeSize >= 1)
        {
            if (edgeSize == 1)
            {
                Graphics.Blit(sourceMask, destinationEffect, edgeMaskMaterial, 1);
            }
            else
            {
                Graphics.Blit(sourceMask, pingA, edgeMaskMaterial, 0);
                for (int p = 1; p < edgeSize - 1; p++)
                {
                    Graphics.Blit(pingA, pingB, edgeMaskMaterial, 0);
                    (pingA, pingB) = (pingB, pingA);
                }
                Graphics.Blit(pingA, destinationEffect, edgeMaskMaterial, 1);
            }
        }

        if (glowRadius >= 1)
        {
            ClearRenderTexture(pingA);
            ClearRenderTexture(pingB);
            float w = GetGlowRingWeight(0, glowRadius, glowWeightFalloff);
            if (w >= glowPassWeightThreshold)
            {
                int pass = 1;
                while (true)
                {
                    glowMaskMaterial.SetFloat(ShaderIDs.PassWeight, w);
                    if (pass == 1)
                    {
                        glowMaskMaterial.SetFloat(ShaderIDs.GlowFirstPass, 1f);
                        Graphics.Blit(sourceMask, pingA, glowMaskMaterial, 0);
                        glowMaskMaterial.SetFloat(ShaderIDs.GlowFirstPass, 0f);
                    }
                    else
                    {
                        Graphics.Blit(pingA, pingB, glowMaskMaterial, 0);
                        (pingA, pingB) = (pingB, pingA);
                    }
                    if (pass >= glowRadius) break;
                    w = GetGlowRingWeight(pass, glowRadius, glowWeightFalloff);
                    pass++;
                    if (w < glowPassWeightThreshold) break;
                }
                // pass 1 只写 .y(辉光),保留 effectRT.x(边缘)
                Graphics.Blit(pingA, destinationEffect, glowMaskMaterial, 1);
            }
        }
    }

    // ---- VA 0x1D1D4CC -------------------------------------------------------
    // 先把当前相机目标拷到 sceneColorRT(供 ActiveBlock 的 _SceneColor 用),
    // 再用 activeBlockMaterial 全屏合成回相机目标。
    // BuiltinRenderTextureType:None=0、CurrentActive=1、CameraTarget=2(APK 元数据实测)。
    private void RefreshSceneColorCommands()
    {
        if (cmd == null) return;
        cmd.Clear();
        cmd.Blit(BuiltinRenderTextureType.CameraTarget, sceneColorRT);
        cmd.Blit(BuiltinRenderTextureType.None, BuiltinRenderTextureType.CameraTarget, activeBlockMaterial);
    }

    // ---- VA 0x1D1D7EC -------------------------------------------------------
    public void BeginTouchBlockFrame()
    {
        if (touchBlockSlots == null) throw new NullReferenceException();
        foreach (var slot in touchBlockSlots)
            if (slot != null) slot.touchedThisFrame = false;
    }

    // ---- VA 0x1D1D83C -------------------------------------------------------
    public void UpdateTouchBlock(int fingerId, Vector2 worldPosition)
    {
        if (touchBlockSlots == null) throw new NullReferenceException();
        foreach (var slot in touchBlockSlots)
            if (slot != null && slot.fingerId == fingerId)
            { slot.touchedThisFrame = true; slot.behavior.UpdatePosition(worldPosition); return; }
        foreach (var slot in touchBlockSlots)
            if (slot != null && slot.fingerId == -1)
            {
                slot.fingerId = fingerId;
                slot.touchedThisFrame = true;
                slot.behavior.UpdatePosition(worldPosition);
                slot.behavior.Show();
                return;
            }
    }

    // ---- VA 0x1D1D9FC -------------------------------------------------------
    public void EndTouchBlockFrame()
    {
        if (touchBlockSlots == null) throw new NullReferenceException();
        foreach (var slot in touchBlockSlots)
            if (slot != null && slot.fingerId != -1 && !slot.touchedThisFrame)
            { slot.behavior.Hide(); slot.fingerId = -1; }
    }

    // ---- VA 0x1D1CEC0 -------------------------------------------------------
    private void UpdateTouchPos()
    {
        var mat = activeBlockMaterial;
        if (mat == null) return;
        mat.SetVectorArray(ShaderIDs.TouchPos, touchPos);
        mat.SetInt(ShaderIDs.TouchPosCount, touchPos.Count);
        // Lerp(lowThreshold, 1, 0.5 + 0.5*sin(shineSpeed*Time.time)) * brightness
        // 0x3E51240 = sinf(PLT→dynsym 实测)
        float shine = Mathf.Lerp(touchShineLowThreshold, 1f,
                                 0.5f + 0.5f * Mathf.Sin(touchShineSpeed * Time.time))
                      * touchShineBrightness;
        mat.SetFloat(ShaderIDs.TouchPosShine, shine);
    }
}

// =============================================================================
public class SubtractBlockPostProcessor : MonoBehaviour
{
    // 字段:0x20 material | 0x28 targetPass | 0x30 cam
    // ---- VA 0x1D1E718 -------------------------------------------------------
    private void OnRenderImage(RenderTexture source, RenderTexture destination)
    {
        if (cam != null && cam.targetTexture == null)
            Graphics.Blit(source, destination);
        else
            Graphics.Blit(source, destination, material, targetPass);
    }
}

// =============================================================================
public partial class ProgressControl : MonoBehaviour
{
    // 字段:0xB8 lowPassFilter | 0xC0 lowPassFilterLerpDuration | 0xC4 lowPassCutoffFrequency
    //       0xC8 lowPassFilterCoroutine
    // ---- VA 0x1D353A8 -------------------------------------------------------
    public void SetLowPassFilter(bool isEnabled)
    {
        if (lowPassFilter == null) return;
        if (lowPassCutoffFrequency <= 0f)
            lowPassCutoffFrequency = lowPassFilter.cutoffFrequency;
        if (lowPassFilterCoroutine != null) StopCoroutine(lowPassFilterCoroutine);
        lowPassFilterCoroutine = StartCoroutine(LerpLowPassFilter(isEnabled));
    }

    // ---- VA 0x1D35474(factory) / 0x1D35510(MoveNext);无 set_Q → Q = 默认 1.0 ----
    private IEnumerator LerpLowPassFilter(bool enableFilter)
    {
        lowPassFilter.enabled = enableFilter;
        float start = lowPassFilter.cutoffFrequency;
        float target = enableFilter ? lowPassCutoffFrequency : 22000f;   // 22000 = .rodata 0xC2654C
        float duration = Mathf.Max(lowPassFilterLerpDuration, 0f);
        float t = 0f;
        while (t < duration)
        {
            t += Time.deltaTime;
            lowPassFilter.cutoffFrequency = Mathf.Lerp(start, target, Mathf.Clamp01(t / duration));
            yield return null;
        }
        lowPassFilter.cutoffFrequency = target;
    }
}

// =============================================================================
// 本文件覆盖的 BlockRender / SubtractBlockPostProcessor / ProgressControl 方法
// 均已从字节确定,无残留占位。
// =============================================================================