using System; using System.Collections.Generic; using System.Collections; using System.IO; using System.Reflection; using System.Text; using UnityEditor; using UnityEditor.Animations; using UnityEngine; using UnityEngine.UI; #if UNITY_2017_1_OR_NEWER using UnityEngine.U2D; using UnityEngine.Playables; #endif #if UNITY_2018_2_OR_NEWER using UnityEditor.U2D; #endif #if UNITY_2017_3_OR_NEWER using UnityEditor.Compilation; #endif #if UNITY_2017_2_OR_NEWER using UnityEngine.Tilemaps; #endif using Object = UnityEngine.Object; namespace AssetUsageDetectorNamespace { public partial class AssetUsageDetector { #region Helper Classes #if UNITY_2017_3_OR_NEWER #pragma warning disable 0649 // The fields' values are assigned via JsonUtility [Serializable] private struct AssemblyDefinitionReferences { public string reference; // Used by AssemblyDefinitionReferenceAssets public List references; // Used by AssemblyDefinitionAssets } #pragma warning restore 0649 #endif #if UNITY_2018_1_OR_NEWER #pragma warning disable 0649 // The fields' values are assigned via JsonUtility [Serializable] private struct ShaderGraphReferences // Used by old Shader Graph serialization format { [Serializable] public struct JSONHolder { public string JSONnodeData; } [Serializable] public class TextureHolder { public string m_SerializedTexture; public string m_SerializedCubemap; public string m_Guid; public string GetTexturePath() { string guid = ExtractGUIDFromString( !string.IsNullOrEmpty( m_SerializedTexture ) ? m_SerializedTexture : m_SerializedCubemap ); if( string.IsNullOrEmpty( guid ) ) guid = m_Guid; return string.IsNullOrEmpty( guid ) ? null : AssetDatabase.GUIDToAssetPath( guid ); } } [Serializable] public struct PropertyData { public string m_Name; public string m_DefaultReferenceName; public string m_OverrideReferenceName; public TextureHolder m_Value; public string GetName() { if( !string.IsNullOrEmpty( m_OverrideReferenceName ) ) return m_OverrideReferenceName; if( !string.IsNullOrEmpty( m_DefaultReferenceName ) ) return m_DefaultReferenceName; if( !string.IsNullOrEmpty( m_Name ) ) return m_Name; return "Property"; } } [Serializable] public struct NodeData { public string m_Name; public string m_FunctionSource; // Custom Function node's Source field public string m_SerializedSubGraph; // Sub-graph node public List m_SerializableSlots; public string GetSubGraphPath() { string guid = ExtractGUIDFromString( m_SerializedSubGraph ); return string.IsNullOrEmpty( guid ) ? null : AssetDatabase.GUIDToAssetPath( guid ); } } [Serializable] public struct NodeSlotData { public TextureHolder m_Texture; public TextureHolder m_TextureArray; public TextureHolder m_Cubemap; public string GetTexturePath() { if( m_Texture != null ) return m_Texture.GetTexturePath(); if( m_Cubemap != null ) return m_Cubemap.GetTexturePath(); if( m_TextureArray != null ) return m_TextureArray.GetTexturePath(); return null; } } public List m_SerializedProperties; public List m_SerializableNodes; // String can be in one of the following formats: // "guid":"GUID_VALUE" // "guid": "GUID_VALUE" // "guid" : "GUID_VALUE" private static string ExtractGUIDFromString( string str ) { if( !string.IsNullOrEmpty( str ) ) { int guidStartIndex = str.IndexOf( "\"guid\"" ); if( guidStartIndex >= 0 ) { guidStartIndex += 6; guidStartIndex = str.IndexOf( '"', guidStartIndex ); if( guidStartIndex > 0 ) { guidStartIndex++; int guidEndIndex = str.IndexOf( '"', guidStartIndex ); if( guidEndIndex > 0 ) return str.Substring( guidStartIndex, guidEndIndex - guidStartIndex ); } } } return null; } } #pragma warning restore 0649 #endif #endregion // Dictionary to quickly find the function to search a specific type with private Dictionary> typeToSearchFunction; // Dictionary to associate special file extensions with their search functions private Dictionary> extensionToSearchFunction; // An optimization to fetch & filter fields and properties of a class only once private readonly Dictionary typeToVariables = new Dictionary( 4096 ); private readonly List validVariables = new List( 32 ); // Path(s) of .cginc, .cg, .hlsl and .glslinc assets in assetsToSearchSet private readonly HashSet shaderIncludesToSearchSet = new HashSet(); #if UNITY_2017_3_OR_NEWER // Path(s) of the Assembly Definition Files in objectsToSearchSet (Value: files themselves) private readonly Dictionary assemblyDefinitionFilesToSearch = new Dictionary( 8 ); #endif // An optimization to fetch an animation clip's curve bindings only once private readonly Dictionary animationClipUniqueBindings = new Dictionary( 256 ); private bool searchPrefabConnections; private bool searchMonoBehavioursForScript; private bool searchRenderers; private bool searchMaterialsForShader; private bool searchTextureReferences; #if UNITY_2018_1_OR_NEWER private bool searchShaderGraphsForSubGraphs; #endif private bool searchSerializableVariablesOnly; private bool prevSearchSerializableVariablesOnly; private BindingFlags fieldModifiers, propertyModifiers; private BindingFlags prevFieldModifiers, prevPropertyModifiers; // Unity's internal function that returns a SerializedProperty's corresponding FieldInfo private delegate FieldInfo FieldInfoGetter( SerializedProperty p, out Type t ); private FieldInfoGetter fieldInfoGetter; private void InitializeSearchFunctionsData( Parameters searchParameters ) { if( typeToSearchFunction == null ) { typeToSearchFunction = new Dictionary>() { { typeof( GameObject ), SearchGameObject }, { typeof( Material ), SearchMaterial }, { typeof( Shader ), SearchShader }, { typeof( MonoScript ), SearchMonoScript }, { typeof( RuntimeAnimatorController ), SearchAnimatorController }, { typeof( AnimatorOverrideController ), SearchAnimatorController }, { typeof( AnimatorController ), SearchAnimatorController }, { typeof( AnimatorStateMachine ), SearchAnimatorStateMachine }, { typeof( AnimatorState ), SearchAnimatorState }, { typeof( AnimatorStateTransition ), SearchAnimatorStateTransition }, { typeof( BlendTree ), SearchBlendTree }, { typeof( AnimationClip ), SearchAnimationClip }, #if UNITY_2017_1_OR_NEWER { typeof( SpriteAtlas ), SearchSpriteAtlas }, #endif }; } if( extensionToSearchFunction == null ) { extensionToSearchFunction = new Dictionary>() { { "compute", SearchShaderSecondaryAsset }, { "cginc", SearchShaderSecondaryAsset }, { "cg", SearchShaderSecondaryAsset }, { "glslinc", SearchShaderSecondaryAsset }, { "hlsl", SearchShaderSecondaryAsset }, #if UNITY_2017_3_OR_NEWER { "asmdef", SearchAssemblyDefinitionFile }, #endif #if UNITY_2019_2_OR_NEWER { "asmref", SearchAssemblyDefinitionFile }, #endif #if UNITY_2018_1_OR_NEWER { "shadergraph", SearchShaderGraph }, { "shadersubgraph", SearchShaderGraph }, #endif }; } fieldModifiers = searchParameters.fieldModifiers | BindingFlags.Instance | BindingFlags.DeclaredOnly; propertyModifiers = searchParameters.propertyModifiers | BindingFlags.Instance | BindingFlags.DeclaredOnly; searchSerializableVariablesOnly = !searchParameters.searchNonSerializableVariables; if( prevFieldModifiers != fieldModifiers || prevPropertyModifiers != propertyModifiers || prevSearchSerializableVariablesOnly != searchSerializableVariablesOnly ) typeToVariables.Clear(); prevFieldModifiers = fieldModifiers; prevPropertyModifiers = propertyModifiers; prevSearchSerializableVariablesOnly = searchSerializableVariablesOnly; searchPrefabConnections = false; searchMonoBehavioursForScript = false; searchRenderers = false; searchMaterialsForShader = false; searchTextureReferences = false; #if UNITY_2018_1_OR_NEWER searchShaderGraphsForSubGraphs = false; #endif foreach( Object obj in objectsToSearchSet ) { if( obj is Texture ) { searchRenderers = true; searchTextureReferences = true; } else if( obj is Material ) searchRenderers = true; else if( obj is MonoScript ) searchMonoBehavioursForScript = true; else if( obj is Shader ) { searchRenderers = true; searchMaterialsForShader = true; } else if( obj is GameObject ) searchPrefabConnections = true; #if UNITY_2017_3_OR_NEWER else if( obj is UnityEditorInternal.AssemblyDefinitionAsset ) assemblyDefinitionFilesToSearch[AssetDatabase.GetAssetPath( obj )] = obj; #endif } foreach( string path in assetsToSearchPathsSet ) { string extension = Utilities.GetFileExtension( path ); if( extension == "hlsl" || extension == "cginc" || extension == "cg" || extension == "glslinc" ) shaderIncludesToSearchSet.Add( path ); #if UNITY_2018_1_OR_NEWER else if( extension == "shadersubgraph" ) searchShaderGraphsForSubGraphs = true; #endif } // AssetDatabase.GetDependencies doesn't take #include lines in shader source codes into consideration. If we are searching for references // of a potential #include target (shaderIncludesToSearchSet), we must search all shader assets and check their #include lines manually if( shaderIncludesToSearchSet.Count > 0 ) { alwaysSearchedExtensionsSet.Add( "shader" ); alwaysSearchedExtensionsSet.Add( "compute" ); alwaysSearchedExtensionsSet.Add( "cginc" ); alwaysSearchedExtensionsSet.Add( "cg" ); alwaysSearchedExtensionsSet.Add( "glslinc" ); alwaysSearchedExtensionsSet.Add( "hlsl" ); } #if UNITY_2017_3_OR_NEWER // AssetDatabase.GetDependencies doesn't return references from Assembly Definition Files to their Assembly Definition References, // so if we are searching for an Assembly Definition File's usages, we must search all Assembly Definition Files' references manually. if( assemblyDefinitionFilesToSearch.Count > 0 ) { alwaysSearchedExtensionsSet.Add( "asmdef" ); #if UNITY_2019_2_OR_NEWER alwaysSearchedExtensionsSet.Add( "asmref" ); #endif } #endif #if UNITY_2018_1_OR_NEWER // AssetDatabase.GetDependencies doesn't work with Shader Graph assets. We must search all Shader Graph assets in the following cases: // searchTextureReferences: to find Texture references used in various nodes and properties // searchShaderGraphsForSubGraphs: to find Shader Sub-graph references in other Shader Graph assets // shaderIncludesToSearchSet: to find .cginc, .cg, .glslinc and .hlsl references used in Custom Function nodes if( searchTextureReferences || searchShaderGraphsForSubGraphs || shaderIncludesToSearchSet.Count > 0 ) { alwaysSearchedExtensionsSet.Add( "shadergraph" ); alwaysSearchedExtensionsSet.Add( "shadersubgraph" ); } #endif #if UNITY_2019_3_OR_NEWER MethodInfo fieldInfoGetterMethod = typeof( Editor ).Assembly.GetType( "UnityEditor.ScriptAttributeUtility" ).GetMethod( "GetFieldInfoAndStaticTypeFromProperty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ); #else MethodInfo fieldInfoGetterMethod = typeof( Editor ).Assembly.GetType( "UnityEditor.ScriptAttributeUtility" ).GetMethod( "GetFieldInfoFromProperty", BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static ); #endif fieldInfoGetter = (FieldInfoGetter) Delegate.CreateDelegate( typeof( FieldInfoGetter ), fieldInfoGetterMethod ); } private ReferenceNode SearchGameObject( Object unityObject ) { GameObject go = (GameObject) unityObject; ReferenceNode referenceNode = PopReferenceNode( go ); // Check if this GameObject's prefab is one of the selected assets if( searchPrefabConnections ) { #if UNITY_2018_3_OR_NEWER Object prefab = PrefabUtility.GetCorrespondingObjectFromSource( go ); #else Object prefab = PrefabUtility.GetPrefabParent( go ); #endif if( objectsToSearchSet.Contains( prefab ) && assetsToSearchRootPrefabs.ContainsFast( prefab as GameObject ) ) referenceNode.AddLinkTo( GetReferenceNode( prefab ), "Prefab object" ); } // Search through all the components of the object Component[] components = go.GetComponents(); for( int i = 0; i < components.Length; i++ ) referenceNode.AddLinkTo( SearchObject( components[i] ) ); return referenceNode; } private ReferenceNode SearchComponent( Object unityObject ) { Component component = (Component) unityObject; // Ignore Transform component (no object field to search for) if( component is Transform ) return null; ReferenceNode referenceNode = PopReferenceNode( component ); if( searchMonoBehavioursForScript && component is MonoBehaviour ) { // If a searched asset is script, check if this component is an instance of it MonoScript script = MonoScript.FromMonoBehaviour( (MonoBehaviour) component ); if( objectsToSearchSet.Contains( script ) ) referenceNode.AddLinkTo( GetReferenceNode( script ) ); } else if( component is ParticleSystemRenderer ) { // Search ParticleSystemRenderer's custom meshes for references (they aren't searched by SerializedObject, unfortunately) Mesh[] meshes = new Mesh[( (ParticleSystemRenderer) component ).meshCount]; int meshCount = ( (ParticleSystemRenderer) component ).GetMeshes( meshes ); for( int i = 0; i < meshCount; i++ ) referenceNode.AddLinkTo( SearchObject( meshes[i] ), "Renderer Module: Mesh" ); } else if( component is ParticleSystem ) { // Some ParticleSystem properties aren't searched by SerializedObject, search them manually instead ParticleSystem particleSystem = (ParticleSystem) component; referenceNode.AddLinkTo( SearchObject( particleSystem.lights.light ), "Light Module: Light" ); try { ParticleSystem.CollisionModule collisionModule = particleSystem.collision; #if UNITY_2020_2_OR_NEWER for( int i = 0, j = collisionModule.planeCount; i < j; i++ ) #else for( int i = 0, j = collisionModule.maxPlaneCount; i < j; i++ ) #endif referenceNode.AddLinkTo( SearchObject( collisionModule.GetPlane( i ) ), "Collision Module: Plane" ); } catch { } try { ParticleSystem.TriggerModule triggerModule = particleSystem.trigger; #if UNITY_2020_2_OR_NEWER for( int i = 0, j = triggerModule.colliderCount; i < j; i++ ) #else for( int i = 0, j = triggerModule.maxColliderCount; i < j; i++ ) #endif referenceNode.AddLinkTo( SearchObject( triggerModule.GetCollider( i ) ), "Trigger Module: Collider" ); } catch { } } else if( searchRenderers && component is Renderer ) { // If an asset is a shader, texture or material, and this component is a Renderer, // search it for references Material[] materials = ( (Renderer) component ).sharedMaterials; for( int i = 0; i < materials.Length; i++ ) referenceNode.AddLinkTo( SearchObject( materials[i] ) ); } else if( component is Animation ) { // Search animation clips for references foreach( AnimationState anim in (Animation) component ) referenceNode.AddLinkTo( SearchObject( anim.clip ) ); // Search the objects that are animated by this Animation component for references SearchAnimatedObjects( referenceNode ); } else if( component is Animator ) { // Search animation clips for references (via AnimatorController) referenceNode.AddLinkTo( SearchObject( ( (Animator) component ).runtimeAnimatorController ) ); // Search the objects that are animated by this Animator component for references SearchAnimatedObjects( referenceNode ); } #if UNITY_2017_2_OR_NEWER else if( component is Tilemap ) { // Search the tiles for references TileBase[] tiles = new TileBase[( (Tilemap) component ).GetUsedTilesCount()]; ( (Tilemap) component ).GetUsedTilesNonAlloc( tiles ); if( tiles != null ) { for( int i = 0; i < tiles.Length; i++ ) referenceNode.AddLinkTo( SearchObject( tiles[i] ), "Tile" ); } } #endif #if UNITY_2017_1_OR_NEWER else if( component is PlayableDirector ) { // Search the PlayableAsset's scene bindings for references PlayableAsset playableAsset = ( (PlayableDirector) component ).playableAsset; if( playableAsset != null && !playableAsset.Equals( null ) ) { foreach( PlayableBinding binding in playableAsset.outputs ) referenceNode.AddLinkTo( SearchObject( ( (PlayableDirector) component ).GetGenericBinding( binding.sourceObject ) ), "Binding: " + binding.streamName ); } } #endif SearchVariablesWithSerializedObject( referenceNode ); return referenceNode; } private ReferenceNode SearchMaterial( Object unityObject ) { Material material = (Material) unityObject; ReferenceNode referenceNode = PopReferenceNode( material ); if( searchMaterialsForShader && objectsToSearchSet.Contains( material.shader ) ) referenceNode.AddLinkTo( GetReferenceNode( material.shader ), "Shader" ); if( searchTextureReferences ) { // Search through all the textures attached to this material // Credit: http://answers.unity3d.com/answers/1116025/view.html Shader shader = material.shader; int shaderPropertyCount = ShaderUtil.GetPropertyCount( shader ); for( int i = 0; i < shaderPropertyCount; i++ ) { if( ShaderUtil.GetPropertyType( shader, i ) == ShaderUtil.ShaderPropertyType.TexEnv ) { string propertyName = ShaderUtil.GetPropertyName( shader, i ); Texture assignedTexture = material.GetTexture( propertyName ); if( objectsToSearchSet.Contains( assignedTexture ) ) referenceNode.AddLinkTo( GetReferenceNode( assignedTexture ), "Shader property: " + propertyName ); } } } return referenceNode; } // Searches default Texture values assigned to shader properties, as well as #include references in shader source code private ReferenceNode SearchShader( Object unityObject ) { Shader shader = (Shader) unityObject; ReferenceNode referenceNode = PopReferenceNode( shader ); if( searchTextureReferences ) { ShaderImporter shaderImporter = AssetImporter.GetAtPath( AssetDatabase.GetAssetPath( unityObject ) ) as ShaderImporter; if( shaderImporter != null ) { int shaderPropertyCount = ShaderUtil.GetPropertyCount( shader ); for( int i = 0; i < shaderPropertyCount; i++ ) { if( ShaderUtil.GetPropertyType( shader, i ) == ShaderUtil.ShaderPropertyType.TexEnv ) { string propertyName = ShaderUtil.GetPropertyName( shader, i ); Texture defaultTexture = shaderImporter.GetDefaultTexture( propertyName ); #if UNITY_2018_1_OR_NEWER if( !defaultTexture ) defaultTexture = shaderImporter.GetNonModifiableTexture( propertyName ); #endif if( objectsToSearchSet.Contains( defaultTexture ) ) referenceNode.AddLinkTo( GetReferenceNode( defaultTexture ), "Default Texture: " + propertyName ); } } } } // Search shader source code for #include references if( shaderIncludesToSearchSet.Count > 0 ) SearchShaderSourceCodeForCGIncludes( referenceNode ); return referenceNode; } // Searches .compute, .cginc, .cg, .hlsl and .glslinc assets for #include references private ReferenceNode SearchShaderSecondaryAsset( Object unityObject ) { if( shaderIncludesToSearchSet.Count == 0 ) return null; ReferenceNode referenceNode = PopReferenceNode( unityObject ); SearchShaderSourceCodeForCGIncludes( referenceNode ); return referenceNode; } // Searches default UnityEngine.Object values assigned to script variables private ReferenceNode SearchMonoScript( Object unityObject ) { MonoScript script = (MonoScript) unityObject; Type scriptType = script.GetClass(); if( scriptType == null || ( !scriptType.IsSubclassOf( typeof( MonoBehaviour ) ) && !scriptType.IsSubclassOf( typeof( ScriptableObject ) ) ) ) return null; MonoImporter scriptImporter = AssetImporter.GetAtPath( AssetDatabase.GetAssetPath( unityObject ) ) as MonoImporter; if( scriptImporter == null ) return null; ReferenceNode referenceNode = PopReferenceNode( script ); VariableGetterHolder[] variables = GetFilteredVariablesForType( scriptType ); for( int i = 0; i < variables.Length; i++ ) { if( variables[i].isSerializable && !variables[i].isProperty ) { Object defaultValue = scriptImporter.GetDefaultReference( variables[i].name ); if( objectsToSearchSet.Contains( defaultValue ) ) referenceNode.AddLinkTo( GetReferenceNode( defaultValue ), "Default variable value: " + variables[i].name ); } } return referenceNode; } private ReferenceNode SearchAnimatorController( Object unityObject ) { RuntimeAnimatorController controller = (RuntimeAnimatorController) unityObject; ReferenceNode referenceNode = PopReferenceNode( controller ); if( controller is AnimatorController ) { AnimatorControllerLayer[] layers = ( (AnimatorController) controller ).layers; for( int i = 0; i < layers.Length; i++ ) { if( objectsToSearchSet.Contains( layers[i].avatarMask ) ) referenceNode.AddLinkTo( GetReferenceNode( layers[i].avatarMask ), layers[i].name + " Mask" ); referenceNode.AddLinkTo( SearchObject( layers[i].stateMachine ) ); } } else { if( controller is AnimatorOverrideController ) { RuntimeAnimatorController parentController = ( (AnimatorOverrideController) controller ).runtimeAnimatorController; if( objectsToSearchSet.Contains( parentController ) ) referenceNode.AddLinkTo( GetReferenceNode( parentController ) ); } AnimationClip[] animClips = controller.animationClips; for( int i = 0; i < animClips.Length; i++ ) referenceNode.AddLinkTo( SearchObject( animClips[i] ) ); } return referenceNode; } private ReferenceNode SearchAnimatorStateMachine( Object unityObject ) { AnimatorStateMachine animatorStateMachine = (AnimatorStateMachine) unityObject; ReferenceNode referenceNode = PopReferenceNode( animatorStateMachine ); ChildAnimatorStateMachine[] stateMachines = animatorStateMachine.stateMachines; for( int i = 0; i < stateMachines.Length; i++ ) referenceNode.AddLinkTo( SearchObject( stateMachines[i].stateMachine ), "Child State Machine" ); ChildAnimatorState[] states = animatorStateMachine.states; for( int i = 0; i < states.Length; i++ ) referenceNode.AddLinkTo( SearchObject( states[i].state ) ); if( searchMonoBehavioursForScript ) { StateMachineBehaviour[] behaviours = animatorStateMachine.behaviours; for( int i = 0; i < behaviours.Length; i++ ) { MonoScript script = MonoScript.FromScriptableObject( behaviours[i] ); if( objectsToSearchSet.Contains( script ) ) referenceNode.AddLinkTo( GetReferenceNode( script ) ); } } return referenceNode; } private ReferenceNode SearchAnimatorState( Object unityObject ) { AnimatorState animatorState = (AnimatorState) unityObject; ReferenceNode referenceNode = PopReferenceNode( animatorState ); referenceNode.AddLinkTo( SearchObject( animatorState.motion ), "Motion" ); if( searchMonoBehavioursForScript ) { StateMachineBehaviour[] behaviours = animatorState.behaviours; for( int i = 0; i < behaviours.Length; i++ ) { MonoScript script = MonoScript.FromScriptableObject( behaviours[i] ); if( objectsToSearchSet.Contains( script ) ) referenceNode.AddLinkTo( GetReferenceNode( script ) ); } } return referenceNode; } private ReferenceNode SearchAnimatorStateTransition( Object unityObject ) { // Don't search AnimatorStateTransition objects, it will just return duplicate results of SearchAnimatorStateMachine return PopReferenceNode( unityObject ); } private ReferenceNode SearchBlendTree( Object unityObject ) { BlendTree blendTree = (BlendTree) unityObject; ReferenceNode referenceNode = PopReferenceNode( blendTree ); ChildMotion[] children = blendTree.children; for( int i = 0; i < children.Length; i++ ) referenceNode.AddLinkTo( SearchObject( children[i].motion ), "Motion" ); return referenceNode; } private ReferenceNode SearchAnimationClip( Object unityObject ) { AnimationClip clip = (AnimationClip) unityObject; ReferenceNode referenceNode = PopReferenceNode( clip ); // Get all curves from animation clip EditorCurveBinding[] objectCurves = AnimationUtility.GetObjectReferenceCurveBindings( clip ); for( int i = 0; i < objectCurves.Length; i++ ) { // Search through all the keyframes in this curve ObjectReferenceKeyframe[] keyframes = AnimationUtility.GetObjectReferenceCurve( clip, objectCurves[i] ); for( int j = 0; j < keyframes.Length; j++ ) referenceNode.AddLinkTo( SearchObject( keyframes[j].value ), "Keyframe: " + keyframes[j].time ); } // Get all events from animation clip AnimationEvent[] events = AnimationUtility.GetAnimationEvents( clip ); for( int i = 0; i < events.Length; i++ ) referenceNode.AddLinkTo( SearchObject( events[i].objectReferenceParameter ), "AnimationEvent: " + events[i].time ); return referenceNode; } #if UNITY_2017_1_OR_NEWER private ReferenceNode SearchSpriteAtlas( Object unityObject ) { SpriteAtlas spriteAtlas = (SpriteAtlas) unityObject; ReferenceNode referenceNode = PopReferenceNode( spriteAtlas ); SerializedObject spriteAtlasSO = new SerializedObject( spriteAtlas ); if( spriteAtlas.isVariant ) { Object masterAtlas = spriteAtlasSO.FindProperty( "m_MasterAtlas" ).objectReferenceValue; if( objectsToSearchSet.Contains( masterAtlas ) ) referenceNode.AddLinkTo( SearchObject( masterAtlas ), "Master Atlas" ); } #if UNITY_2018_2_OR_NEWER Object[] packables = spriteAtlas.GetPackables(); if( packables != null ) { for( int i = 0; i < packables.Length; i++ ) referenceNode.AddLinkTo( SearchObject( packables[i] ), "Packed Texture" ); } #else SerializedProperty packables = spriteAtlasSO.FindProperty( "m_EditorData.packables" ); if( packables != null ) { for( int i = 0, length = packables.arraySize; i < length; i++ ) referenceNode.AddLinkTo( SearchObject( packables.GetArrayElementAtIndex( i ).objectReferenceValue ), "Packed Texture" ); } #endif return referenceNode; } #endif #if UNITY_2017_3_OR_NEWER // Find references from an Assembly Definition File to its Assembly Definition References private ReferenceNode SearchAssemblyDefinitionFile( Object unityObject ) { if( assemblyDefinitionFilesToSearch.Count == 0 ) return null; AssemblyDefinitionReferences assemblyDefinitionFile = JsonUtility.FromJson( ( (TextAsset) unityObject ).text ); ReferenceNode referenceNode = PopReferenceNode( unityObject ); if( !string.IsNullOrEmpty( assemblyDefinitionFile.reference ) ) { if( assemblyDefinitionFile.references == null ) assemblyDefinitionFile.references = new List( 1 ) { assemblyDefinitionFile.reference }; else assemblyDefinitionFile.references.Add( assemblyDefinitionFile.reference ); } if( assemblyDefinitionFile.references != null ) { for( int i = 0; i < assemblyDefinitionFile.references.Count; i++ ) { #if UNITY_2019_1_OR_NEWER string assemblyPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyReference( assemblyDefinitionFile.references[i] ); #else string assemblyPath = CompilationPipeline.GetAssemblyDefinitionFilePathFromAssemblyName( assemblyDefinitionFile.references[i] ); #endif if( !string.IsNullOrEmpty( assemblyPath ) ) { Object searchedAssemblyDefinitionFile; if( assemblyDefinitionFilesToSearch.TryGetValue( assemblyPath, out searchedAssemblyDefinitionFile ) ) referenceNode.AddLinkTo( GetReferenceNode( searchedAssemblyDefinitionFile ), "Referenced Assembly" ); } } } return referenceNode; } #endif #if UNITY_2018_1_OR_NEWER // Searches Shader Graph assets for references private ReferenceNode SearchShaderGraph( Object unityObject ) { if( !searchTextureReferences && !searchShaderGraphsForSubGraphs && shaderIncludesToSearchSet.Count == 0 ) return null; ReferenceNode referenceNode = PopReferenceNode( unityObject ); // Shader Graph assets are JSON files, they must be crawled manually to find references string graphJson = File.ReadAllText( AssetDatabase.GetAssetPath( unityObject ) ); if( graphJson.IndexOf( "\"m_ObjectId\"", 0, Mathf.Min( 200, graphJson.Length ) ) >= 0 ) { // New Shader Graph serialization format is used: https://github.com/Unity-Technologies/Graphics/pull/222 // Iterate over all these occurrences: "guid\": \"GUID_VALUE\" (\" is used instead of " because it is a nested JSON) IterateOverValuesInString( graphJson, "\"guid\\\"", '"', ( guid ) => { if( guid.Length > 1 ) { if( guid[guid.Length - 1] == '\\' ) guid = guid.Substring( 0, guid.Length - 1 ); string referencePath = AssetDatabase.GUIDToAssetPath( guid ); if( !string.IsNullOrEmpty( referencePath ) && assetsToSearchPathsSet.Contains( referencePath ) ) { Object reference = AssetDatabase.LoadMainAssetAtPath( referencePath ); if( objectsToSearchSet.Contains( reference ) ) referenceNode.AddLinkTo( GetReferenceNode( reference ), "Used in graph" ); } } } ); if( shaderIncludesToSearchSet.Count > 0 ) { // Iterate over all these occurrences: "m_FunctionSource": "GUID_VALUE" (this one is not nested JSON) IterateOverValuesInString( graphJson, "\"m_FunctionSource\"", '"', ( guid ) => { string referencePath = AssetDatabase.GUIDToAssetPath( guid ); if( !string.IsNullOrEmpty( referencePath ) && assetsToSearchPathsSet.Contains( referencePath ) ) { Object reference = AssetDatabase.LoadMainAssetAtPath( referencePath ); if( objectsToSearchSet.Contains( reference ) ) referenceNode.AddLinkTo( GetReferenceNode( reference ), "Used in node: Custom Function" ); } } ); } } else { // Old Shader Graph serialization format is used. Although we could use the same search method as the new serialization format (which // is potentially faster), this alternative search method yields more information about references ShaderGraphReferences shaderGraph = JsonUtility.FromJson( graphJson ); if( shaderGraph.m_SerializedProperties != null ) { for( int i = shaderGraph.m_SerializedProperties.Count - 1; i >= 0; i-- ) { string propertyJSON = shaderGraph.m_SerializedProperties[i].JSONnodeData; if( string.IsNullOrEmpty( propertyJSON ) ) continue; ShaderGraphReferences.PropertyData propertyData = JsonUtility.FromJson( propertyJSON ); if( propertyData.m_Value == null ) continue; string texturePath = propertyData.m_Value.GetTexturePath(); if( string.IsNullOrEmpty( texturePath ) || !assetsToSearchPathsSet.Contains( texturePath ) ) continue; Texture texture = AssetDatabase.LoadAssetAtPath( texturePath ); if( objectsToSearchSet.Contains( texture ) ) referenceNode.AddLinkTo( GetReferenceNode( texture ), "Default Texture: " + propertyData.GetName() ); } } if( shaderGraph.m_SerializableNodes != null ) { for( int i = shaderGraph.m_SerializableNodes.Count - 1; i >= 0; i-- ) { string nodeJSON = shaderGraph.m_SerializableNodes[i].JSONnodeData; if( string.IsNullOrEmpty( nodeJSON ) ) continue; ShaderGraphReferences.NodeData nodeData = JsonUtility.FromJson( nodeJSON ); if( !string.IsNullOrEmpty( nodeData.m_FunctionSource ) ) { string customFunctionPath = AssetDatabase.GUIDToAssetPath( nodeData.m_FunctionSource ); if( !string.IsNullOrEmpty( customFunctionPath ) && assetsToSearchPathsSet.Contains( customFunctionPath ) ) { Object customFunction = AssetDatabase.LoadMainAssetAtPath( customFunctionPath ); if( objectsToSearchSet.Contains( customFunction ) ) referenceNode.AddLinkTo( GetReferenceNode( customFunction ), "Used in node: " + nodeData.m_Name ); } } if( searchShaderGraphsForSubGraphs ) { string subGraphPath = nodeData.GetSubGraphPath(); if( !string.IsNullOrEmpty( subGraphPath ) && assetsToSearchPathsSet.Contains( subGraphPath ) ) { Object subGraph = AssetDatabase.LoadMainAssetAtPath( subGraphPath ); if( objectsToSearchSet.Contains( subGraph ) ) referenceNode.AddLinkTo( GetReferenceNode( subGraph ), "Used as Sub-graph" ); } } if( nodeData.m_SerializableSlots == null ) continue; for( int j = nodeData.m_SerializableSlots.Count - 1; j >= 0; j-- ) { string nodeSlotJSON = nodeData.m_SerializableSlots[j].JSONnodeData; if( string.IsNullOrEmpty( nodeSlotJSON ) ) continue; string texturePath = JsonUtility.FromJson( nodeSlotJSON ).GetTexturePath(); if( string.IsNullOrEmpty( texturePath ) || !assetsToSearchPathsSet.Contains( texturePath ) ) continue; Texture texture = AssetDatabase.LoadAssetAtPath( texturePath ); if( objectsToSearchSet.Contains( texture ) ) referenceNode.AddLinkTo( GetReferenceNode( texture ), "Used in node: " + nodeData.m_Name ); } } } } return referenceNode; } #endif // Find references from an Animation/Animator component to the objects that it animates private void SearchAnimatedObjects( ReferenceNode referenceNode ) { GameObject root = ( (Component) referenceNode.nodeObject ).gameObject; AnimationClip[] clips = AnimationUtility.GetAnimationClips( root ); for( int i = 0; i < clips.Length; i++ ) { AnimationClip clip = clips[i]; bool isClipUnique = true; for( int j = i - 1; j >= 0; j-- ) { if( clips[j] == clip ) { isClipUnique = false; break; } } if( !isClipUnique ) continue; EditorCurveBinding[] uniqueBindings; if( !animationClipUniqueBindings.TryGetValue( clip, out uniqueBindings ) ) { // Calculate all the "unique" paths that the animation clip's curves have // Both float curves (GetCurveBindings) and object reference curves (GetObjectReferenceCurveBindings) are checked List _uniqueBindings = new List( 2 ); EditorCurveBinding[] bindings = AnimationUtility.GetCurveBindings( clip ); for( int j = 0; j < bindings.Length; j++ ) { string bindingPath = bindings[j].path; if( string.IsNullOrEmpty( bindingPath ) ) // Ignore the root animated object continue; bool isBindingUnique = true; for( int k = _uniqueBindings.Count - 1; k >= 0; k-- ) { if( bindingPath == _uniqueBindings[k].path ) { isBindingUnique = false; break; } } if( isBindingUnique ) _uniqueBindings.Add( bindings[j] ); } bindings = AnimationUtility.GetObjectReferenceCurveBindings( clip ); for( int j = 0; j < bindings.Length; j++ ) { string bindingPath = bindings[j].path; if( string.IsNullOrEmpty( bindingPath ) ) // Ignore the root animated object continue; bool isBindingUnique = true; for( int k = _uniqueBindings.Count - 1; k >= 0; k-- ) { if( bindingPath == _uniqueBindings[k].path ) { isBindingUnique = false; break; } } if( isBindingUnique ) _uniqueBindings.Add( bindings[j] ); } uniqueBindings = _uniqueBindings.ToArray(); animationClipUniqueBindings[clip] = uniqueBindings; } string clipName = clip.name; for( int j = 0; j < uniqueBindings.Length; j++ ) referenceNode.AddLinkTo( SearchObject( AnimationUtility.GetAnimatedObject( root, uniqueBindings[j] ) ), "Animated via clip: " + clipName ); } } // Search #include references in shader source code private void SearchShaderSourceCodeForCGIncludes( ReferenceNode referenceNode ) { string shaderPath = AssetDatabase.GetAssetPath( (Object) referenceNode.nodeObject ); // Iterate over all these occurrences: #include: "INCLUDE_REFERENCE" IterateOverValuesInString( File.ReadAllText( shaderPath ), "#include ", '"', ( include ) => { bool isIncludePotentialReference = shaderIncludesToSearchSet.Contains( include ); if( !isIncludePotentialReference ) { // Get absolute path of the #include include = Path.GetFullPath( Path.Combine( Path.GetDirectoryName( shaderPath ), include ) ); int trimStartLength = Directory.GetCurrentDirectory().Length + 1; // Convert absolute path to a Project-relative path if( include.Length > trimStartLength ) { include = include.Substring( trimStartLength ).Replace( '\\', '/' ); isIncludePotentialReference = shaderIncludesToSearchSet.Contains( include ); } } if( isIncludePotentialReference ) { Object cgShader = AssetDatabase.LoadMainAssetAtPath( include ); if( objectsToSearchSet.Contains( cgShader ) ) referenceNode.AddLinkTo( GetReferenceNode( cgShader ), "Used with #include" ); } } ); } // Search through variables of an object with SerializedObject private void SearchVariablesWithSerializedObject( ReferenceNode referenceNode ) { if( !isInPlayMode || referenceNode.nodeObject.IsAsset() ) { SerializedObject so = new SerializedObject( (Object) referenceNode.nodeObject ); SerializedProperty iterator = so.GetIterator(); SerializedProperty iteratorVisible = so.GetIterator(); if( iterator.Next( true ) ) { string assetPath = AssetDatabase.GetAssetPath( (Object) referenceNode.nodeObject ); bool isProjectSettings = !string.IsNullOrEmpty( assetPath ) && assetPath.StartsWithFast( "ProjectSettings/" ); bool iteratingVisible = iteratorVisible.NextVisible( true ); bool enterChildren; do { if( isProjectSettings ) { // Search Project Settings in full because for some reason, important settings like SRP asset // slot in Quality Settings aren't searched when iterating over only visible properties enterChildren = true; } else { // Iterate over NextVisible properties AND the properties that have corresponding FieldInfos (internal Unity // properties don't have FieldInfos so we are skipping them, which is good because search results found in // those properties aren't interesting and mostly confusing) bool isVisible = iteratingVisible && SerializedProperty.EqualContents( iterator, iteratorVisible ); if( isVisible ) iteratingVisible = iteratorVisible.NextVisible( true ); else { Type propFieldType; isVisible = iterator.type == "Array" || fieldInfoGetter( iterator, out propFieldType ) != null; } if( !isVisible ) { enterChildren = false; continue; } } ReferenceNode searchResult; switch( iterator.propertyType ) { case SerializedPropertyType.ObjectReference: searchResult = SearchObject( iterator.objectReferenceValue ); enterChildren = false; break; case SerializedPropertyType.ExposedReference: searchResult = SearchObject( iterator.exposedReferenceValue ); enterChildren = false; break; #if UNITY_2019_3_OR_NEWER case SerializedPropertyType.ManagedReference: searchResult = SearchObject( GetRawSerializedPropertyValue( iterator ) ); enterChildren = false; break; #endif case SerializedPropertyType.Generic: searchResult = null; enterChildren = true; break; default: searchResult = null; enterChildren = false; break; } if( searchResult != null && searchResult != referenceNode ) { string propertyPath = iterator.propertyPath; // m_RD.texture is a redundant reference that shows up when searching sprites if( !propertyPath.EndsWithFast( "m_RD.texture" ) ) referenceNode.AddLinkTo( searchResult, "Variable: " + propertyPath.Replace( ".Array.data[", "[" ) ); // "arrayVariable.Array.data[0]" becomes "arrayVariable[0]" } } while( iterator.Next( enterChildren ) ); return; } } // Use reflection algorithm as fallback SearchVariablesWithReflection( referenceNode ); } // Search through variables of an object with reflection private void SearchVariablesWithReflection( ReferenceNode referenceNode ) { // Get filtered variables for this object VariableGetterHolder[] variables = GetFilteredVariablesForType( referenceNode.nodeObject.GetType() ); for( int i = 0; i < variables.Length; i++ ) { // When possible, don't search non-serializable variables if( searchSerializableVariablesOnly && !variables[i].isSerializable ) continue; try { object variableValue = variables[i].Get( referenceNode.nodeObject ); if( variableValue == null || variableValue.Equals( null ) ) continue; // Values stored inside ICollection objects are searched using IEnumerable, // no need to have duplicate search entries if( !( variableValue is ICollection ) ) { ReferenceNode searchResult = SearchObject( variableValue ); if( searchResult != null && searchResult != referenceNode ) referenceNode.AddLinkTo( searchResult, ( variables[i].isProperty ? "Property: " : "Variable: " ) + variables[i].name ); } if( variableValue is IEnumerable && !( variableValue is Transform ) ) { // If the field is IEnumerable (possibly an array or collection), search through members of it // Note that Transform IEnumerable (children of the transform) is not iterated int index = 0; foreach( object element in (IEnumerable) variableValue ) { ReferenceNode searchResult = SearchObject( element ); if( searchResult != null && searchResult != referenceNode ) referenceNode.AddLinkTo( searchResult, string.Concat( variables[i].isProperty ? "Property: " : "Variable: ", variables[i].name, "[", index + "]" ) ); index++; } } } catch( UnassignedReferenceException ) { } catch( MissingReferenceException ) { } catch( MissingComponentException ) { } catch( NotImplementedException ) { } catch( Exception e ) { // Unknown exceptions usually occur when variableValue is an IEnumerable and its enumerator throws an unhandled exception in MoveNext or Current StringBuilder sb = new StringBuilder( callStack.Count * 50 + 1000 ); sb.Append( "Skipped searching " ).Append( referenceNode.nodeObject.GetType().FullName ).Append( "." ).Append( variables[i].name ).AppendLine( " because it threw exception:" ).Append( e ).AppendLine(); Object latestUnityObjectInCallStack = AppendCallStackToStringBuilder( sb ); Debug.LogWarning( sb.ToString(), latestUnityObjectInCallStack ); } } } // Get filtered variables for a type private VariableGetterHolder[] GetFilteredVariablesForType( Type type ) { VariableGetterHolder[] result; if( typeToVariables.TryGetValue( type, out result ) ) return result; // This is the first time this type of object is seen, filter and cache its variables // Variable filtering process: // 1- skip Obsolete variables // 2- skip primitive types, enums and strings // 3- skip common Unity types that can't hold any references (e.g. Vector3, Rect, Color, Quaternion) // // P.S. IsIgnoredUnityType() extension function handles steps 2) and 3) validVariables.Clear(); // Filter the fields if( fieldModifiers != ( BindingFlags.Instance | BindingFlags.DeclaredOnly ) ) { Type currType = type; while( currType != typeof( object ) ) { FieldInfo[] fields = currType.GetFields( fieldModifiers ); for( int i = 0; i < fields.Length; i++ ) { FieldInfo field = fields[i]; // Skip obsolete fields if( Attribute.IsDefined( field, typeof( ObsoleteAttribute ) ) ) continue; // Skip primitive types if( field.FieldType.IsIgnoredUnityType() ) continue; // Additional filtering for fields: // 1- Ignore "m_RectTransform", "m_CanvasRenderer" and "m_Canvas" fields of Graphic components string fieldName = field.Name; if( typeof( Graphic ).IsAssignableFrom( currType ) && ( fieldName == "m_RectTransform" || fieldName == "m_CanvasRenderer" || fieldName == "m_Canvas" ) ) continue; VariableGetVal getter = field.CreateGetter( type ); if( getter != null ) validVariables.Add( new VariableGetterHolder( field, getter, searchSerializableVariablesOnly ? field.IsSerializable() : true ) ); } currType = currType.BaseType; } } if( propertyModifiers != ( BindingFlags.Instance | BindingFlags.DeclaredOnly ) ) { Type currType = type; while( currType != typeof( object ) ) { PropertyInfo[] properties = currType.GetProperties( propertyModifiers ); for( int i = 0; i < properties.Length; i++ ) { PropertyInfo property = properties[i]; // Skip obsolete properties if( Attribute.IsDefined( property, typeof( ObsoleteAttribute ) ) ) continue; // Skip primitive types if( property.PropertyType.IsIgnoredUnityType() ) continue; // Skip properties without a getter function MethodInfo propertyGetter = property.GetGetMethod( true ); if( propertyGetter == null ) continue; // Skip indexer properties if( property.GetIndexParameters().Length > 0 ) continue; // No need to check properties with 'override' keyword if( propertyGetter.GetBaseDefinition().DeclaringType != propertyGetter.DeclaringType ) continue; string propertyName = property.Name; // Ignore "gameObject", "transform", "rectTransform" and "attachedRigidbody" properties of components to get more useful results if( typeof( Component ).IsAssignableFrom( currType ) && ( propertyName == "gameObject" || propertyName == "transform" || propertyName == "attachedRigidbody" || propertyName == "rectTransform" ) ) continue; // Ignore "canvasRenderer" and "canvas" properties of Graphic components to get more useful results else if( typeof( Graphic ).IsAssignableFrom( currType ) && ( propertyName == "canvasRenderer" || propertyName == "canvas" ) ) continue; // Prevent accessing properties of Unity that instantiate an existing resource (causing memory leak) else if( typeof( MeshFilter ).IsAssignableFrom( currType ) && propertyName == "mesh" ) continue; // Same as above else if( ( propertyName == "material" || propertyName == "materials" ) && ( typeof( Renderer ).IsAssignableFrom( currType ) || typeof( Collider ).IsAssignableFrom( currType ) || #if !UNITY_2019_3_OR_NEWER #pragma warning disable 0618 typeof( GUIText ).IsAssignableFrom( currType ) || #pragma warning restore 0618 #endif typeof( Collider2D ).IsAssignableFrom( currType ) ) ) continue; // "sharedMaterials" are searched via SearchComponent, no need to search it with reflection, as well else if( typeof( Renderer ).IsAssignableFrom( currType ) && ( propertyName == "sharedMaterial" || propertyName == "sharedMaterials" ) ) continue; // Ignore "parameters" property of Animator since it doesn't contain any useful data and logs a warning to the console when Animator is inactive else if( typeof( Animator ).IsAssignableFrom( currType ) && propertyName == "parameters" ) continue; // Ignore "spriteAnimator" property of TMP_Text component because this property adds a TMP_SpriteAnimator component to the object if it doesn't exist else if( propertyName == "spriteAnimator" && currType.Name == "TMP_Text" ) continue; // Ignore "meshFilter" property of TextMeshPro and TMP_SubMesh components because this property adds a MeshFilter component to the object if it doesn't exist else if( propertyName == "meshFilter" && ( currType.Name == "TextMeshPro" || currType.Name == "TMP_SubMesh" ) ) continue; else { VariableGetVal getter = property.CreateGetter(); if( getter != null ) validVariables.Add( new VariableGetterHolder( property, getter, searchSerializableVariablesOnly ? property.IsSerializable() : true ) ); } } currType = currType.BaseType; } } result = validVariables.ToArray(); // Cache the filtered fields typeToVariables.Add( type, result ); return result; } // Credit: http://answers.unity.com/answers/425602/view.html // Returns the raw System.Object value of a SerializedProperty public object GetRawSerializedPropertyValue( SerializedProperty property ) { object result = property.serializedObject.targetObject; string[] path = property.propertyPath.Replace( ".Array.data[", "[" ).Split( '.' ); for( int i = 0; i < path.Length; i++ ) { string pathElement = path[i]; int arrayStartIndex = pathElement.IndexOf( '[' ); if( arrayStartIndex < 0 ) result = GetFieldValue( result, pathElement ); else { string variableName = pathElement.Substring( 0, arrayStartIndex ); int arrayEndIndex = pathElement.IndexOf( ']', arrayStartIndex + 1 ); int arrayElementIndex = int.Parse( pathElement.Substring( arrayStartIndex + 1, arrayEndIndex - arrayStartIndex - 1 ) ); result = GetFieldValue( result, variableName, arrayElementIndex ); } } return result; } // Credit: http://answers.unity.com/answers/425602/view.html private object GetFieldValue( object source, string fieldName ) { if( source == null ) return null; FieldInfo fieldInfo = null; Type type = source.GetType(); while( fieldInfo == null && type != typeof( object ) ) { fieldInfo = type.GetField( fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly ); type = type.BaseType; } if( fieldInfo != null ) return fieldInfo.GetValue( source ); PropertyInfo propertyInfo = null; type = source.GetType(); while( propertyInfo == null && type != typeof( object ) ) { propertyInfo = type.GetProperty( fieldName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly | BindingFlags.IgnoreCase ); type = type.BaseType; } if( propertyInfo != null ) return propertyInfo.GetValue( source, null ); if( fieldName.Length > 2 && fieldName.StartsWith( "m_", StringComparison.OrdinalIgnoreCase ) ) return GetFieldValue( source, fieldName.Substring( 2 ) ); return null; } // Credit: http://answers.unity.com/answers/425602/view.html private object GetFieldValue( object source, string fieldName, int arrayIndex ) { IEnumerable enumerable = GetFieldValue( source, fieldName ) as IEnumerable; if( enumerable == null ) return null; if( enumerable is IList ) return ( (IList) enumerable )[arrayIndex]; IEnumerator enumerator = enumerable.GetEnumerator(); for( int i = 0; i <= arrayIndex; i++ ) enumerator.MoveNext(); return enumerator.Current; } // Iterates over all occurrences of specific key-value pairs in string // Example1: #include "VALUE" valuePrefix=#include, valueWrapperChar=" // Example2: "guid": "VALUE" valuePrefix="guid", valueWrapperChar=" private void IterateOverValuesInString( string str, string valuePrefix, char valueWrapperChar, Action valueAction ) { int valueStartIndex, valueEndIndex = 0; while( true ) { valueStartIndex = str.IndexOf( valuePrefix, valueEndIndex ); if( valueStartIndex < 0 ) return; valueStartIndex = str.IndexOf( valueWrapperChar, valueStartIndex + valuePrefix.Length ); if( valueStartIndex < 0 ) return; valueStartIndex++; valueEndIndex = str.IndexOf( valueWrapperChar, valueStartIndex ); if( valueEndIndex < 0 ) return; if( valueEndIndex > valueStartIndex ) valueAction( str.Substring( valueStartIndex, valueEndIndex - valueStartIndex ) ); } } } }