Example #1
0
        internal static void Apply(GameObject avatar)
        {
#if VRC_SDK_VRCSDK2 || VRC_SDK_VRCSDK3
            foreach (Component component in AvatarValidation.FindIllegalComponents(avatar))
            {
                Object.DestroyImmediate(component);
            }
#endif
        }
Example #2
0
    // Token: 0x0600577D RID: 22397 RVA: 0x001E2CD8 File Offset: 0x001E10D8
    private GameObject CreateAvatarClone(string name, AvatarCloneType type)
    {
        GameObject gameObject = UnityEngine.Object.Instantiate <GameObject>(this.currentAvatar);

        AvatarValidation.RemoveIllegalComponents(name, gameObject, true);
        gameObject.name               = name;
        gameObject.transform.parent   = this.currentAvatar.transform.parent;
        gameObject.transform.position = this.currentAvatar.transform.position;
        gameObject.transform.rotation = this.currentAvatar.transform.rotation;
        AvatarClone avatarClone = gameObject.AddComponent <AvatarClone>();

        avatarClone.OriginalAvatar = this.currentAvatar;
        avatarClone.CloneType      = type;
        avatarClone.StripComponents();
        Tools.SetLayerRecursively(gameObject, (type != AvatarCloneType.Mirror) ? LayerMask.NameToLayer("PlayerLocal") : LayerMask.NameToLayer("MirrorReflection"), -1);
        return(gameObject);
    }
Example #3
0
    // Token: 0x0600577B RID: 22395 RVA: 0x001E2320 File Offset: 0x001E0720
    private void AttachAvatarInternal(UnityEngine.Object Prefab, string Name, float Scale = 1f)
    {
        this.WasMeasured = false;
        if (this == null || base.gameObject == null || base.GetComponent <Transform>() == null)
        {
            Debug.LogError("AttachAvatarInternal: VRCAvatarManager was destroyed, could not attach avatar: " + Name);
            return;
        }
        GameObject gameObject = AssetManagement.Instantiate <GameObject>(Prefab);

        if (gameObject == null || gameObject.GetComponent <Transform>() == null)
        {
            Debug.LogError("Could not instantiate avatar " + Name);
            return;
        }
        AvatarValidation.RemoveIllegalComponents(Name, gameObject, true);
        AvatarValidation.EnforceAudioSourceLimits(gameObject);
        if (!this.localPlayer)
        {
            foreach (Camera camera in gameObject.GetComponentsInChildren <Camera>())
            {
                Debug.LogWarning("Removing camera from " + camera.gameObject.name);
                UnityEngine.Object.Destroy(camera);
            }
        }
        int num = 2;

        VRC.Network.AssignNetworkIDsToObject(gameObject, false, VRC.Network.GetOwnerId(base.gameObject).Value, ref num);
        VRC.Network.IsObjectReady(gameObject);
        base.transform.localPosition       = Vector3.zero;
        base.transform.localRotation       = Quaternion.identity;
        gameObject.transform.parent        = base.transform;
        gameObject.transform.localPosition = new Vector3(0f, 0f, 0f);
        gameObject.transform.localRotation = new Quaternion(0f, 0f, 0f, 1f);
        gameObject.transform.localScale   *= Scale;
        VRC_AvatarDescriptor component = gameObject.GetComponent <VRC_AvatarDescriptor>();

        if (component == null)
        {
            Debug.LogError("Avatar Missing Descriptor");
            Analytics.Send(ApiAnalyticEvent.EventType.error, "Avatar Missing Descriptor: " + Name, null, null);
            UnityEngine.Object.Destroy(gameObject);
            return;
        }
        if (gameObject.GetComponent <Animator>() == null)
        {
            Debug.Log("Avatar Missing Animator");
            if (base.transform.parent != null)
            {
                Debug.Log("Avatar player object name=" + base.transform.parent.name);
            }
            Analytics.Send(ApiAnalyticEvent.EventType.error, "Avatar Missing Animator: " + Name, null, null);
        }
        if (this._avatarMirrorClone != null)
        {
            this.DestroyAvatarRenderClone();
        }
        if (this.currentAvatar != null)
        {
            VRC_EventHandler[] componentsInChildren2 = this.currentAvatar.GetComponentsInChildren <VRC_EventHandler>();
            foreach (VRC_EventHandler vrc_EventHandler in componentsInChildren2)
            {
                vrc_EventHandler.Unregister();
            }
            if (this.currentAvatar.GetComponent <Transform>() != null)
            {
                this.currentAvatar.GetComponent <Transform>().parent = null;
            }
            UnityEngine.Object.Destroy(this.currentAvatar);
        }
        this.currentAvatar           = gameObject;
        this.currentAvatarDescriptor = component;
        AudioListener[] componentsInChildren3 = gameObject.GetComponentsInChildren <AudioListener>();
        foreach (AudioListener audioListener in componentsInChildren3)
        {
            audioListener.enabled = false;
        }
        if (!this.localPlayer)
        {
            Camera[] componentsInChildren4 = gameObject.GetComponentsInChildren <Camera>();
            foreach (Camera camera2 in componentsInChildren4)
            {
                camera2.enabled = false;
            }
        }
        AudioListener[] componentsInChildren5 = gameObject.GetComponentsInChildren <AudioListener>();
        foreach (AudioListener audioListener2 in componentsInChildren5)
        {
            audioListener2.enabled = false;
        }
        SkinnedMeshRenderer[] componentsInChildren6 = this.currentAvatar.GetComponentsInChildren <SkinnedMeshRenderer>();
        foreach (SkinnedMeshRenderer skinnedMeshRenderer in componentsInChildren6)
        {
            skinnedMeshRenderer.updateWhenOffscreen = true;
        }
        this.visibleElements      = gameObject.GetComponentsInChildren <Renderer>(true);
        this.visibleElementEnable = new bool[this.visibleElements.Length];
        for (int num2 = 0; num2 < this.visibleElements.Length; num2++)
        {
            this.visibleElementEnable[num2] = this.visibleElements[num2].enabled;
        }
        List <IVRCCullable> list = new List <IVRCCullable>();

        foreach (DynamicBone item in gameObject.GetComponentsInChildren <DynamicBone>(true))
        {
            list.Add(item);
        }
        this.cullableElements   = list.ToArray();
        this._laserSelectRegion = null;
        if (!this.localPlayer && this._vrcPlayer != null)
        {
            Transform transform = this._vrcPlayer.transform.Find("SelectRegion");
            if (transform != null)
            {
                this._laserSelectRegion = transform.gameObject.GetComponent <PlayerSelector>();
            }
        }
        Animator component2 = this.currentAvatar.GetComponent <Animator>();

        if (component2 != null && component2.avatar != null)
        {
            if (component2.isHuman)
            {
                this._nonHuman = false;
                this.currentAvatar.GetOrAddComponent <VRIK>();
                this.currentAvatar.GetOrAddComponent <FullBodyBipedIK>();
                this.currentAvatar.GetOrAddComponent <global::LimbIK>();
                component2.runtimeAnimatorController = null;
                if (component.Animations == VRC_AvatarDescriptor.AnimationSet.Female)
                {
                    this._useFemaleAnims = true;
                }
                else
                {
                    this._useFemaleAnims = false;
                }
                AnimatorOverrideController defaults = this.animatorControllerMale;
                if (this._useFemaleAnims)
                {
                    defaults = this.animatorControllerFemale;
                }
                AnimatorOverrideController defaults2 = this.animatorControllerMaleSitting;
                if (this._useFemaleAnims)
                {
                    defaults2 = this.animatorControllerFemaleSitting;
                }
                this.currentStandingAnims            = this.MergeAnims("CustomStanding", defaults, component.CustomStandingAnims);
                this.currentSittingAnims             = this.MergeAnims("CustomSitting", defaults2, component.CustomSittingAnims);
                component2.runtimeAnimatorController = this.currentStandingAnims;
                component2.updateMode = AnimatorUpdateMode.Normal;
                if (this.currentStandingAnims == null)
                {
                    Debug.LogError(((!(this._vrcPlayer != null)) ? base.gameObject.name : this._vrcPlayer.name) + " has human avatar with broken standing animations.", this);
                    this.currentStandingAnims = this.animatorControllerMale;
                }
                else if (this.currentSittingAnims == null)
                {
                    Debug.LogError(((!(this._vrcPlayer != null)) ? base.gameObject.name : this._vrcPlayer.name) + " has human avatar with broken sitting animations.", this);
                    this.currentSittingAnims = this.animatorControllerMaleSitting;
                }
            }
            else
            {
                this._nonHuman = true;
                if (this.localPlayer)
                {
                    VRCTrackingManager.SetAvatarViewPoint(this.currentAvatarDescriptor.ViewPosition, Vector3.zero);
                }
                this.currentAvatarArmLength = VRCTracking.DefaultArmLength;
                this.currentAvatarEyeHeight = this.currentAvatarDescriptor.ViewPosition.y;
                if (this.localPlayer)
                {
                    VRCTrackingManager.AdjustViewPositionToAvatar();
                }
            }
            if (this.localPlayer)
            {
                component2.cullingMode = AnimatorCullingMode.AlwaysAnimate;
            }
            else
            {
                component2.cullingMode = AnimatorCullingMode.CullUpdateTransforms;
            }
            component2.applyRootMotion = false;
        }
        else
        {
            this._nonHuman = true;
            if (this.localPlayer)
            {
                VRCTrackingManager.SetAvatarViewPoint(this.currentAvatarDescriptor.ViewPosition, Vector3.zero);
            }
            this.currentAvatarArmLength = VRCTracking.DefaultArmLength;
            this.currentAvatarEyeHeight = this.currentAvatarDescriptor.ViewPosition.y;
            if (this.localPlayer)
            {
                VRCTrackingManager.AdjustViewPositionToAvatar();
            }
        }
        Transform transform2 = base.transform.Find("../CameraMount");

        if (transform2 != null && base.transform.parent != null)
        {
            transform2.position = base.transform.parent.TransformPoint(new Vector3(this.currentAvatarDescriptor.ViewPosition.x / base.transform.parent.localScale.x, this.currentAvatarDescriptor.ViewPosition.y / base.transform.parent.localScale.y, 0f));
        }
        MeshRenderer[] componentsInChildren8 = this.currentAvatar.GetComponentsInChildren <MeshRenderer>();
        foreach (MeshRenderer meshRenderer in componentsInChildren8)
        {
            meshRenderer.lightProbeUsage = this.useLightProbes;
        }
        SkinnedMeshRenderer[] componentsInChildren9 = this.currentAvatar.GetComponentsInChildren <SkinnedMeshRenderer>();
        foreach (SkinnedMeshRenderer skinnedMeshRenderer2 in componentsInChildren9)
        {
            skinnedMeshRenderer2.lightProbeUsage     = this.useLightProbes;
            skinnedMeshRenderer2.updateWhenOffscreen = this.localPlayer;
        }
        Vector3 localPosition = new Vector3(0f, this.currentAvatarDescriptor.ViewPosition.y + 0.5f, 0f);

        if (this.profile != null)
        {
            this.profile.localPosition = localPosition;
        }
        if (this.old_profile != null)
        {
            this.old_profile.localPosition = localPosition;
        }
        if (this.localPlayer)
        {
            Tools.SetLayerRecursively(gameObject, LayerMask.NameToLayer("PlayerLocal"), LayerMask.NameToLayer("UiMenu"));
        }
        else
        {
            Tools.SetLayerRecursively(gameObject, LayerMask.NameToLayer("Player"), LayerMask.NameToLayer("UiMenu"));
        }
        if (this.localPlayer)
        {
            this.InitAvatarRenderClone();
        }
        this._isAttached           = true;
        this.localAvatarVisibility = VRCAvatarManager.LocalAvatarVisibility.Visible;
        this.RefreshLocalAvatarVisibility();
        if (this.OnAvatarCreated != null)
        {
            this.OnAvatarCreated(this.currentAvatar, this.currentAvatarDescriptor, false);
        }
    }
        public override void ValidateFeatures(VRC_AvatarDescriptor avatar, Animator anim, AvatarPerformanceStats perfStats)
        {
            //Create avatar debug hashset
            VRCAvatarDescriptor avatarSDK3 = avatar as VRCAvatarDescriptor;

            if (avatarSDK3 != null)
            {
                avatarSDK3.animationHashSet.Clear();

                foreach (VRCAvatarDescriptor.CustomAnimLayer animLayer in avatarSDK3.baseAnimationLayers)
                {
                    AnimatorController controller = animLayer.animatorController as AnimatorController;
                    if (controller != null)
                    {
                        foreach (AnimatorControllerLayer layer in controller.layers)
                        {
                            ProcessStateMachine(layer.stateMachine, "");
                            void ProcessStateMachine(AnimatorStateMachine stateMachine, string prefix)
                            {
                                //Update prefix
                                prefix = prefix + stateMachine.name + ".";

                                //States
                                foreach (var state in stateMachine.states)
                                {
                                    VRCAvatarDescriptor.DebugHash hash = new VRCAvatarDescriptor.DebugHash();
                                    string fullName = prefix + state.state.name;
                                    hash.hash = Animator.StringToHash(fullName);
                                    hash.name = fullName.Remove(0, layer.stateMachine.name.Length + 1);
                                    avatarSDK3.animationHashSet.Add(hash);
                                }

                                //Sub State Machines
                                foreach (var subMachine in stateMachine.stateMachines)
                                {
                                    ProcessStateMachine(subMachine.stateMachine, prefix);
                                }
                            }
                        }
                    }
                }
            }

            //Validate Playable Layers
            if (avatarSDK3 != null && avatarSDK3.customizeAnimationLayers)
            {
                VRCAvatarDescriptor.CustomAnimLayer gestureLayer = avatarSDK3.baseAnimationLayers[2];
                if (anim != null &&
                    anim.isHuman &&
                    gestureLayer.animatorController != null &&
                    gestureLayer.type == VRCAvatarDescriptor.AnimLayerType.Gesture &&
                    !gestureLayer.isDefault)
                {
                    AnimatorController controller = gestureLayer.animatorController as AnimatorController;
                    if (controller != null && controller.layers[0].avatarMask == null)
                    {
                        _builder.OnGUIError(avatar, "Gesture Layer needs valid mask on first animator layer",
                                            delegate { OpenAnimatorControllerWindow(controller); }, null);
                    }
                }
            }

            //Expression menu images
            if (avatarSDK3 != null)
            {
                bool ValidateTexture(Texture2D texture)
                {
                    string          path     = AssetDatabase.GetAssetPath(texture);
                    TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;

                    if (importer == null)
                    {
                        return(true);
                    }
                    TextureImporterPlatformSettings settings = importer.GetDefaultPlatformTextureSettings();

                    //Max texture size
                    if ((texture.width > MAX_ACTION_TEXTURE_SIZE || texture.height > MAX_ACTION_TEXTURE_SIZE) &&
                        settings.maxTextureSize > MAX_ACTION_TEXTURE_SIZE)
                    {
                        return(false);
                    }

                    //Compression
                    if (settings.textureCompression == TextureImporterCompression.Uncompressed)
                    {
                        return(false);
                    }

                    //Success
                    return(true);
                }

                void FixTexture(Texture2D texture)
                {
                    string          path     = AssetDatabase.GetAssetPath(texture);
                    TextureImporter importer = AssetImporter.GetAtPath(path) as TextureImporter;

                    if (importer == null)
                    {
                        return;
                    }
                    TextureImporterPlatformSettings settings = importer.GetDefaultPlatformTextureSettings();

                    //Max texture size
                    if (texture.width > MAX_ACTION_TEXTURE_SIZE || texture.height > MAX_ACTION_TEXTURE_SIZE)
                    {
                        settings.maxTextureSize = Math.Min(settings.maxTextureSize, MAX_ACTION_TEXTURE_SIZE);
                    }

                    //Compression
                    if (settings.textureCompression == TextureImporterCompression.Uncompressed)
                    {
                        settings.textureCompression = TextureImporterCompression.Compressed;
                    }

                    //Set & Reimport
                    importer.SetPlatformTextureSettings(settings);
                    AssetDatabase.ImportAsset(path);
                }

                //Find all textures
                List <Texture2D>          textures  = new List <Texture2D>();
                List <VRCExpressionsMenu> menuStack = new List <VRCExpressionsMenu>();
                FindTextures(avatarSDK3.expressionsMenu);

                void FindTextures(VRCExpressionsMenu menu)
                {
                    if (menu == null || menuStack.Contains(menu)) //Prevent recursive menu searching
                    {
                        return;
                    }
                    menuStack.Add(menu);

                    //Check controls
                    foreach (VRCExpressionsMenu.Control control in menu.controls)
                    {
                        AddTexture(control.icon);
                        if (control.labels != null)
                        {
                            foreach (VRCExpressionsMenu.Control.Label label in control.labels)
                            {
                                AddTexture(label.icon);
                            }
                        }

                        if (control.subMenu != null)
                        {
                            FindTextures(control.subMenu);
                        }
                    }

                    void AddTexture(Texture2D texture)
                    {
                        if (texture != null)
                        {
                            textures.Add(texture);
                        }
                    }
                }

                //Validate
                bool isValid = true;
                foreach (Texture2D texture in textures)
                {
                    if (!ValidateTexture(texture))
                    {
                        isValid = false;
                    }
                }

                if (!isValid)
                {
                    _builder.OnGUIError(avatar, "Images used for Actions & Moods are too large.",
                                        delegate { Selection.activeObject = avatar.gameObject; }, FixTextures);
                }

                //Fix
                void FixTextures()
                {
                    foreach (Texture2D texture in textures)
                    {
                        FixTexture(texture);
                    }
                }
            }

            //Expression menu parameters
            if (avatarSDK3 != null)
            {
                //Check for expression menu/parameters object
                if (avatarSDK3.expressionsMenu != null || avatarSDK3.expressionParameters != null)
                {
                    //Menu
                    if (avatarSDK3.expressionsMenu == null)
                    {
                        _builder.OnGUIError(avatar, "VRCExpressionsMenu object reference is missing.",
                                            delegate { Selection.activeObject = avatarSDK3; }, null);
                    }

                    //Parameters
                    if (avatarSDK3.expressionParameters == null)
                    {
                        _builder.OnGUIError(avatar, "VRCExpressionParameters object reference is missing.",
                                            delegate { Selection.activeObject = avatarSDK3; }, null);
                    }
                }

                //Check if parameters is valid
                if (avatarSDK3.expressionParameters != null && avatarSDK3.expressionParameters.CalcTotalCost() > VRCExpressionParameters.MAX_PARAMETER_COST)
                {
                    _builder.OnGUIError(avatar, "VRCExpressionParameters has too many parameters defined.",
                                        delegate { Selection.activeObject = avatarSDK3.expressionParameters; }, null);
                }

                //Find all existing parameters
                if (avatarSDK3.expressionsMenu != null && avatarSDK3.expressionParameters != null)
                {
                    List <VRCExpressionsMenu> menuStack  = new List <VRCExpressionsMenu>();
                    List <string>             parameters = new List <string>();
                    List <VRCExpressionsMenu> selects    = new List <VRCExpressionsMenu>();
                    FindParameters(avatarSDK3.expressionsMenu);

                    void FindParameters(VRCExpressionsMenu menu)
                    {
                        if (menu == null || menuStack.Contains(menu)) //Prevent recursive menu searching
                        {
                            return;
                        }
                        menuStack.Add(menu);

                        //Check controls
                        foreach (VRCExpressionsMenu.Control control in menu.controls)
                        {
                            AddParameter(control.parameter);
                            if (control.subParameters != null)
                            {
                                foreach (VRCExpressionsMenu.Control.Parameter subParameter in control.subParameters)
                                {
                                    AddParameter(subParameter);
                                }
                            }

                            if (control.subMenu != null)
                            {
                                FindParameters(control.subMenu);
                            }
                        }

                        void AddParameter(VRCExpressionsMenu.Control.Parameter parameter)
                        {
                            if (parameter != null)
                            {
                                parameters.Add(parameter.name);
                                selects.Add(menu);
                            }
                        }
                    }

                    //Validate parameters
                    for (int i = 0; i < parameters.Count; i++)
                    {
                        string             parameter = parameters[i];
                        VRCExpressionsMenu select    = selects[i];

                        //Find
                        bool exists = string.IsNullOrEmpty(parameter) || avatarSDK3.expressionParameters.FindParameter(parameter) != null;
                        if (!exists)
                        {
                            _builder.OnGUIError(avatar,
                                                "VRCExpressionsMenu uses a parameter that is not defined.\nParameter: " + parameter,
                                                delegate { Selection.activeObject = select; }, null);
                        }
                    }

                    //Validate param choices
                    foreach (var menu in menuStack)
                    {
                        foreach (var control in menu.controls)
                        {
                            bool isValid = true;
                            if (control.type == VRCExpressionsMenu.Control.ControlType.FourAxisPuppet)
                            {
                                isValid &= ValidateNonBoolParam(control.subParameters[0].name);
                                isValid &= ValidateNonBoolParam(control.subParameters[1].name);
                                isValid &= ValidateNonBoolParam(control.subParameters[2].name);
                                isValid &= ValidateNonBoolParam(control.subParameters[3].name);
                            }
                            else if (control.type == VRCExpressionsMenu.Control.ControlType.RadialPuppet)
                            {
                                isValid &= ValidateNonBoolParam(control.subParameters[0].name);
                            }
                            else if (control.type == VRCExpressionsMenu.Control.ControlType.TwoAxisPuppet)
                            {
                                isValid &= ValidateNonBoolParam(control.subParameters[0].name);
                                isValid &= ValidateNonBoolParam(control.subParameters[1].name);
                            }
                            if (!isValid)
                            {
                                _builder.OnGUIError(avatar,
                                                    "VRCExpressionsMenu uses an invalid parameter for a control.\nControl: " + control.name,
                                                    delegate { Selection.activeObject = menu; }, null);
                            }
                        }

                        bool ValidateNonBoolParam(string name)
                        {
                            VRCExpressionParameters.Parameter param = string.IsNullOrEmpty(name) ? null : avatarSDK3.expressionParameters.FindParameter(name);
                            if (param != null && param.valueType == VRCExpressionParameters.ValueType.Bool)
                            {
                                return(false);
                            }
                            return(true);
                        }
                    }
                }
            }

            IEnumerable <Component> componentsToRemove      = AvatarValidation.FindIllegalComponents(avatar.gameObject);
            HashSet <string>        componentsToRemoveNames = new HashSet <string>();
            IEnumerable <Component> toRemove = componentsToRemove as Component[] ?? componentsToRemove.ToArray();

            foreach (Component c in toRemove)
            {
                if (componentsToRemoveNames.Contains(c.GetType().Name) == false)
                {
                    componentsToRemoveNames.Add(c.GetType().Name);
                }
            }

            if (componentsToRemoveNames.Count > 0)
            {
                _builder.OnGUIError(avatar,
                                    "The following component types are found on the Avatar and will be removed by the client: " +
                                    string.Join(", ", componentsToRemoveNames.ToArray()),
                                    delegate { ShowRestrictedComponents(toRemove); },
                                    delegate { FixRestrictedComponents(toRemove); });
            }

            List <AudioSource> audioSources =
                avatar.gameObject.GetComponentsInChildren <AudioSource>(true).ToList();

            if (audioSources.Count > 0)
            {
                _builder.OnGUIWarning(avatar,
                                      "Audio sources found on Avatar, they will be adjusted to safe limits, if necessary.",
                                      GetAvatarSubSelectAction(avatar, typeof(AudioSource)), null);
            }

            List <VRCStation> stations =
                avatar.gameObject.GetComponentsInChildren <VRCStation>(true).ToList();

            if (stations.Count > 0)
            {
                _builder.OnGUIWarning(avatar, "Stations found on Avatar, they will be adjusted to safe limits, if necessary.",
                                      GetAvatarSubSelectAction(avatar, typeof(VRCStation)), null);
            }

            if (VRCSdkControlPanel.HasSubstances(avatar.gameObject))
            {
                _builder.OnGUIWarning(avatar,
                                      "This avatar has one or more Substance materials, which is not supported and may break in-game. Please bake your Substances to regular materials.",
                                      () => { Selection.objects = VRCSdkControlPanel.GetSubstanceObjects(avatar.gameObject); },
                                      null);
            }

            CheckAvatarMeshesForLegacyBlendShapesSetting(avatar);

#if UNITY_ANDROID
            IEnumerable <Shader> illegalShaders = AvatarValidation.FindIllegalShaders(avatar.gameObject);
            foreach (Shader s in illegalShaders)
            {
                _builder.OnGUIError(avatar, "Avatar uses unsupported shader '" + s.name + "'. You can only use the shaders provided in 'VRChat/Mobile' for Quest avatars.", delegate() { Selection.activeObject
                                                                                                                                                                                             = avatar.gameObject; }, null);
            }
#endif

            foreach (AvatarPerformanceCategory perfCategory in Enum.GetValues(typeof(AvatarPerformanceCategory)))
            {
                if (perfCategory == AvatarPerformanceCategory.Overall ||
                    perfCategory == AvatarPerformanceCategory.PolyCount ||
                    perfCategory == AvatarPerformanceCategory.AABB ||
                    perfCategory == AvatarPerformanceCategory.AvatarPerformanceCategoryCount)
                {
                    continue;
                }

                Action show = null;

                switch (perfCategory)
                {
                case AvatarPerformanceCategory.AnimatorCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(Animator));
                    break;

                case AvatarPerformanceCategory.AudioSourceCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(AudioSource));
                    break;

                case AvatarPerformanceCategory.BoneCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(SkinnedMeshRenderer));
                    break;

                case AvatarPerformanceCategory.ClothCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(Cloth));
                    break;

                case AvatarPerformanceCategory.ClothMaxVertices:
                    show = GetAvatarSubSelectAction(avatar, typeof(Cloth));
                    break;

                case AvatarPerformanceCategory.LightCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(Light));
                    break;

                case AvatarPerformanceCategory.LineRendererCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(LineRenderer));
                    break;

                case AvatarPerformanceCategory.MaterialCount:
                    show = GetAvatarSubSelectAction(avatar,
                                                    new[] { typeof(MeshRenderer), typeof(SkinnedMeshRenderer) });
                    break;

                case AvatarPerformanceCategory.MeshCount:
                    show = GetAvatarSubSelectAction(avatar,
                                                    new[] { typeof(MeshRenderer), typeof(SkinnedMeshRenderer) });
                    break;

                case AvatarPerformanceCategory.ParticleCollisionEnabled:
                    show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem));
                    break;

                case AvatarPerformanceCategory.ParticleMaxMeshPolyCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem));
                    break;

                case AvatarPerformanceCategory.ParticleSystemCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem));
                    break;

                case AvatarPerformanceCategory.ParticleTotalCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem));
                    break;

                case AvatarPerformanceCategory.ParticleTrailsEnabled:
                    show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem));
                    break;

                case AvatarPerformanceCategory.PhysicsColliderCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(Collider));
                    break;

                case AvatarPerformanceCategory.PhysicsRigidbodyCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(Rigidbody));
                    break;

                case AvatarPerformanceCategory.PolyCount:
                    show = GetAvatarSubSelectAction(avatar,
                                                    new[] { typeof(MeshRenderer), typeof(SkinnedMeshRenderer) });
                    break;

                case AvatarPerformanceCategory.SkinnedMeshCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(SkinnedMeshRenderer));
                    break;

                case AvatarPerformanceCategory.TrailRendererCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(TrailRenderer));
                    break;
                }

                // we can only show these buttons if DynamicBone is installed

                Type dynamicBoneType         = typeof(AvatarValidation).Assembly.GetType("DynamicBone");
                Type dynamicBoneColliderType = typeof(AvatarValidation).Assembly.GetType("DynamicBoneCollider");
                if ((dynamicBoneType != null) && (dynamicBoneColliderType != null))
                {
                    switch (perfCategory)
                    {
                    case AvatarPerformanceCategory.DynamicBoneColliderCount:
                        show = GetAvatarSubSelectAction(avatar, dynamicBoneColliderType);
                        break;

                    case AvatarPerformanceCategory.DynamicBoneCollisionCheckCount:
                        show = GetAvatarSubSelectAction(avatar, dynamicBoneColliderType);
                        break;

                    case AvatarPerformanceCategory.DynamicBoneComponentCount:
                        show = GetAvatarSubSelectAction(avatar, dynamicBoneType);
                        break;

                    case AvatarPerformanceCategory.DynamicBoneSimulatedBoneCount:
                        show = GetAvatarSubSelectAction(avatar, dynamicBoneType);
                        break;
                    }
                }

                OnGUIPerformanceInfo(avatar, perfStats, perfCategory, show, null);
            }

            _builder.OnGUILink(avatar, "Avatar Optimization Tips", VRCSdkControlPanel.AVATAR_OPTIMIZATION_TIPS_URL);
        }
        public override void ValidateFeatures(VRC.SDKBase.VRC_AvatarDescriptor avatar, Animator anim, AvatarPerformanceStats perfStats)
        {
            IEnumerable <Component> componentsToRemove      = AvatarValidation.FindIllegalComponents(avatar.gameObject);
            HashSet <string>        componentsToRemoveNames = new HashSet <string>();
            IEnumerable <Component> toRemove = componentsToRemove as Component[] ?? componentsToRemove.ToArray();

            foreach (Component c in toRemove)
            {
                if (componentsToRemoveNames.Contains(c.GetType().Name) == false)
                {
                    componentsToRemoveNames.Add(c.GetType().Name);
                }
            }

            if (componentsToRemoveNames.Count > 0)
            {
                _builder.OnGUIError(avatar,
                                    "The following component types are found on the Avatar and will be removed by the client: " +
                                    string.Join(", ", componentsToRemoveNames.ToArray()),
                                    delegate { ShowRestrictedComponents(toRemove); },
                                    delegate { FixRestrictedComponents(toRemove); });
            }

            List <AudioSource> audioSources =
                avatar.gameObject.GetComponentsInChildren <AudioSource>(true).ToList();

            if (audioSources.Count > 0)
            {
                _builder.OnGUIWarning(avatar,
                                      "Audio sources found on Avatar, they will be adjusted to safe limits, if necessary.",
                                      GetAvatarSubSelectAction(avatar, typeof(AudioSource)), null);
            }

            List <VRCStation> stations =
                avatar.gameObject.GetComponentsInChildren <VRCStation>(true).ToList();

            if (stations.Count > 0)
            {
                _builder.OnGUIWarning(avatar, "Stations found on Avatar, they will be adjusted to safe limits, if necessary.",
                                      GetAvatarSubSelectAction(avatar, typeof(VRCStation)), null);
            }

            if (VRCSdkControlPanel.HasSubstances(avatar.gameObject))
            {
                _builder.OnGUIWarning(avatar,
                                      "This avatar has one or more Substance materials, which is not supported and may break in-game. Please bake your Substances to regular materials.",
                                      () => { Selection.objects = VRCSdkControlPanel.GetSubstanceObjects(avatar.gameObject); },
                                      null);
            }

            CheckAvatarMeshesForLegacyBlendShapesSetting(avatar);

#if UNITY_ANDROID
            IEnumerable <Shader> illegalShaders = AvatarValidation.FindIllegalShaders(avatar.gameObject);
            foreach (Shader s in illegalShaders)
            {
                _builder.OnGUIError(avatar, "Avatar uses unsupported shader '" + s.name + "'. You can only use the shaders provided in 'VRChat/Mobile' for Quest avatars.", delegate() { Selection.activeObject
                                                                                                                                                                                             = avatar.gameObject; }, null);
            }
#endif

            foreach (AvatarPerformanceCategory perfCategory in Enum.GetValues(typeof(AvatarPerformanceCategory)))
            {
                if (perfCategory == AvatarPerformanceCategory.Overall ||
                    perfCategory == AvatarPerformanceCategory.PolyCount ||
                    perfCategory == AvatarPerformanceCategory.AABB ||
                    perfCategory == AvatarPerformanceCategory.AvatarPerformanceCategoryCount)
                {
                    continue;
                }

                Action show = null;

                switch (perfCategory)
                {
                case AvatarPerformanceCategory.AnimatorCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(Animator));
                    break;

                case AvatarPerformanceCategory.AudioSourceCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(AudioSource));
                    break;

                case AvatarPerformanceCategory.BoneCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(SkinnedMeshRenderer));
                    break;

                case AvatarPerformanceCategory.ClothCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(Cloth));
                    break;

                case AvatarPerformanceCategory.ClothMaxVertices:
                    show = GetAvatarSubSelectAction(avatar, typeof(Cloth));
                    break;

                case AvatarPerformanceCategory.LightCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(Light));
                    break;

                case AvatarPerformanceCategory.LineRendererCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(LineRenderer));
                    break;

                case AvatarPerformanceCategory.MaterialCount:
                    show = GetAvatarSubSelectAction(avatar,
                                                    new[] { typeof(MeshRenderer), typeof(SkinnedMeshRenderer) });
                    break;

                case AvatarPerformanceCategory.MeshCount:
                    show = GetAvatarSubSelectAction(avatar,
                                                    new[] { typeof(MeshRenderer), typeof(SkinnedMeshRenderer) });
                    break;

                case AvatarPerformanceCategory.ParticleCollisionEnabled:
                    show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem));
                    break;

                case AvatarPerformanceCategory.ParticleMaxMeshPolyCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem));
                    break;

                case AvatarPerformanceCategory.ParticleSystemCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem));
                    break;

                case AvatarPerformanceCategory.ParticleTotalCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem));
                    break;

                case AvatarPerformanceCategory.ParticleTrailsEnabled:
                    show = GetAvatarSubSelectAction(avatar, typeof(ParticleSystem));
                    break;

                case AvatarPerformanceCategory.PhysicsColliderCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(Collider));
                    break;

                case AvatarPerformanceCategory.PhysicsRigidbodyCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(Rigidbody));
                    break;

                case AvatarPerformanceCategory.PolyCount:
                    show = GetAvatarSubSelectAction(avatar,
                                                    new[] { typeof(MeshRenderer), typeof(SkinnedMeshRenderer) });
                    break;

                case AvatarPerformanceCategory.SkinnedMeshCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(SkinnedMeshRenderer));
                    break;

                case AvatarPerformanceCategory.TrailRendererCount:
                    show = GetAvatarSubSelectAction(avatar, typeof(TrailRenderer));
                    break;
                }

                // we can only show these buttons if DynamicBone is installed

                Type dynamicBoneType         = typeof(AvatarValidation).Assembly.GetType("DynamicBone");
                Type dynamicBoneColliderType = typeof(AvatarValidation).Assembly.GetType("DynamicBoneCollider");
                if ((dynamicBoneType != null) && (dynamicBoneColliderType != null))
                {
                    switch (perfCategory)
                    {
                    case AvatarPerformanceCategory.DynamicBoneColliderCount:
                        show = GetAvatarSubSelectAction(avatar, dynamicBoneColliderType);
                        break;

                    case AvatarPerformanceCategory.DynamicBoneCollisionCheckCount:
                        show = GetAvatarSubSelectAction(avatar, dynamicBoneColliderType);
                        break;

                    case AvatarPerformanceCategory.DynamicBoneComponentCount:
                        show = GetAvatarSubSelectAction(avatar, dynamicBoneType);
                        break;

                    case AvatarPerformanceCategory.DynamicBoneSimulatedBoneCount:
                        show = GetAvatarSubSelectAction(avatar, dynamicBoneType);
                        break;
                    }
                }

                OnGUIPerformanceInfo(avatar, perfStats, perfCategory, show, null);
            }

            _builder.OnGUILink(avatar, "Avatar Optimization Tips", VRCSdkControlPanel.AVATAR_OPTIMIZATION_TIPS_URL);
        }