using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; using UnityEngine; using System.Reflection; namespace VRC.SDKBase.Validation { public static class ValidationUtils { private static string _EDITOR_ONLY_TAG = "EditorOnly"; private static bool IsEditorOnly(Component component) { if (component.CompareTag(_EDITOR_ONLY_TAG)) { return true; } foreach (var t in component.transform.GetComponentsInParent(true)) { if (t.CompareTag(_EDITOR_ONLY_TAG)) { return true; } } return false; } public static List GetComponentsInChildrenExcludingEditorOnly(this GameObject target, bool includeInactive) where T : Component { List foundComponents = new List(); T[] allComponents = target.GetComponentsInChildren(includeInactive); foreach (T component in allComponents) { if (component == null || IsEditorOnly(component)) { continue; } foundComponents.Add(component); } return foundComponents; } public static List GetComponentsInChildrenExcludingEditorOnly(this GameObject target, Type type, bool includeInactive) { List foundComponents = new List(); Component[] allComponents = target.GetComponentsInChildren(type, includeInactive); foreach (Component component in allComponents) { if (component == null || IsEditorOnly(component)) { continue; } foundComponents.Add(component); } return foundComponents; } public static void RemoveIllegalComponents(GameObject target, HashSet whitelist, bool retry = true, bool onlySceneObjects = false, bool logStripping = true) { List foundComponents = FindIllegalComponents(target, whitelist); foreach(Component component in foundComponents) { if(component == null) { continue; } if(onlySceneObjects && component.GetInstanceID() < 0) { continue; } if(logStripping) { Core.Logger.LogWarning($"Removing {component.GetType().Name} comp from {component.gameObject.name}"); } RemoveComponent(component); } } public static List FindIllegalComponents(GameObject target, HashSet whitelist) { List components = target.GetComponentsInChildrenExcludingEditorOnly(true); for (int i = components.Count - 1; i >= 0; i--) { Component component = components[i]; if (component == null || whitelist.Contains(component.GetType()) || component is IEditorOnly) { components.RemoveAt(i); } } return components; } private static readonly Dictionary> _whitelistCache = new Dictionary>(); public static HashSet WhitelistedTypes(string whitelistName, IEnumerable componentTypeWhitelist) { if (_whitelistCache.ContainsKey(whitelistName)) { return _whitelistCache[whitelistName]; } Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); HashSet whitelist = new HashSet(); foreach(string whitelistedTypeName in componentTypeWhitelist) { Type whitelistedType = TypeUtils.GetTypeFromName(whitelistedTypeName, assemblies); if(whitelistedType == null) { continue; } if(whitelist.Contains(whitelistedType)) { continue; } whitelist.Add(whitelistedType); } AddDerivedClasses(whitelist); _whitelistCache[whitelistName] = whitelist; return _whitelistCache[whitelistName]; } public static HashSet WhitelistedTypes(string whitelistName, IEnumerable componentTypeWhitelist) { if (_whitelistCache.ContainsKey(whitelistName)) { return _whitelistCache[whitelistName]; } HashSet whitelist = new HashSet(); whitelist.UnionWith(componentTypeWhitelist); AddDerivedClasses(whitelist); _whitelistCache[whitelistName] = whitelist; return _whitelistCache[whitelistName]; } private static void AddDerivedClasses(HashSet whitelist) { Assembly[] assemblies = AppDomain.CurrentDomain.GetAssemblies(); foreach(Assembly assembly in assemblies) { foreach(Type type in assembly.GetTypes()) { if(whitelist.Contains(type)) { continue; } if(!typeof(Component).IsAssignableFrom(type)) { continue; } Type currentType = type; while(currentType != typeof(object) && currentType != null) { if(whitelist.Contains(currentType)) { whitelist.Add(type); break; } currentType = currentType.BaseType; } } } } private static readonly Dictionary> _requireComponentsCache = new Dictionary>(); private static void RemoveDependencies(Component rootComponent) { if (rootComponent == null) { return; } Component[] components = rootComponent.GetComponents(); if (components == null || components.Length == 0) { return; } Type compType = rootComponent.GetType(); foreach (var siblingComponent in components) { if (siblingComponent == null) { continue; } Type siblingComponentType = siblingComponent.GetType(); if(!_requireComponentsCache.TryGetValue(siblingComponentType, out ImmutableArray requiredComponentAttributes)) { requiredComponentAttributes = siblingComponentType.GetCustomAttributes(typeof(RequireComponent), true).Cast().ToImmutableArray(); _requireComponentsCache.Add(siblingComponentType, requiredComponentAttributes); } bool deleteMe = false; foreach (RequireComponent requireComponent in requiredComponentAttributes) { if (requireComponent == null) { continue; } bool needsDeletion(Type reqType) { if (reqType == null) return false; // check if the rootComponent fullfills this type requirement of siblingComponent if (!reqType.IsAssignableFrom(compType)) return false; // check if we're _the only one_ fullfilling it, as there might be subclasses of the base reqType we do allow // in which case we don't need to remove this sibling var fullfilledByOthers = false; foreach (var candidate in components) { if (candidate == null) continue; if (candidate == siblingComponent) continue; if (candidate == rootComponent) continue; if (reqType.IsAssignableFrom(candidate.GetType())) fullfilledByOthers = true; } if (fullfilledByOthers) return false; // something else fullfills us, we can stay return true; } deleteMe |= needsDeletion(requireComponent.m_Type0); deleteMe |= deleteMe || needsDeletion(requireComponent.m_Type1); deleteMe |= deleteMe || needsDeletion(requireComponent.m_Type2); if (deleteMe) break; } if (deleteMe && siblingComponent != rootComponent) { RemoveComponent(siblingComponent); } } } public static void RemoveComponent(Component comp) { if (comp == null) { return; } RemoveDependencies(comp); #if VRC_CLIENT UnityEngine.Object.DestroyImmediate(comp, true); #else UnityEngine.Object.DestroyImmediate(comp, false); #endif } public static void RemoveComponentsOfType(GameObject target) where T : Component { if (target == null) { return; } foreach (T comp in target.GetComponentsInChildren(true)) { if (comp == null) { continue; } RemoveComponent(comp); } } } }