using System; using System.Collections.Generic; using System.Linq; //using TMPro; using UnityEngine; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityExplorer.Helpers; using UnityExplorer.Inspectors; using UnityExplorer.UI.Shared; using UnityExplorer.Unstrip; using System.Reflection; #if CPP using UnhollowerRuntimeLib; #endif namespace UnityExplorer.UI.Modules { internal enum SearchContext { UnityObject, GameObject, Component, Custom, Singleton, StaticClass } internal enum SceneFilter { Any, Asset, DontDestroyOnLoad, Explicit, } internal enum ChildFilter { Any, RootObject, HasParent } public class SearchPage : MainMenu.Page { public override string Name => "Search"; public static SearchPage Instance; internal SearchContext m_context; private SceneFilter m_sceneFilter; private ChildFilter m_childFilter; internal bool m_isStaticClassSearching; // ui elements private Text m_resultCountText; private InputField m_customTypeInput; private InputField m_nameInput; private Button m_selectedContextButton; private readonly Dictionary m_contextButtons = new Dictionary(); private Dropdown m_sceneDropdown; private int m_lastSceneCount = -1; private GameObject m_extraFilterRow; // Results internal object[] m_results; internal readonly List m_resultShortList = new List(); private int m_lastCount; public PageHandler m_resultListPageHandler; private GameObject m_resultListContent; private readonly List m_resultListTexts = new List(); public SearchPage() { Instance = this; } public override void Init() { ConstructUI(); } public void OnSceneChange() { m_results = new object[0]; RefreshResultList(); } public override void Update() { if (HaveScenesChanged()) { RefreshSceneDropdown(); } if (m_customTypeInput.isFocused && m_context != SearchContext.Custom) { OnContextButtonClicked(SearchContext.Custom); } } // Updating result list content private void RefreshResultList() { m_resultListPageHandler.ListCount = m_results.Length; int newCount = 0; foreach (var itemIndex in m_resultListPageHandler) { newCount++; // normalized index starting from 0 var i = itemIndex - m_resultListPageHandler.StartIndex; if (itemIndex >= m_results.Length) { if (i > m_lastCount || i >= m_resultListTexts.Count) break; GameObject label = m_resultListTexts[i].transform.parent.parent.gameObject; if (label.activeSelf) label.SetActive(false); } else { var obj = m_results[itemIndex]; var unityObj = obj as UnityEngine.Object; if (obj == null || (unityObj != null && !unityObj)) continue; if (i >= m_resultShortList.Count) { m_resultShortList.Add(obj); AddResultButton(); } else { m_resultShortList[i] = obj; } var text = m_resultListTexts[i]; if (m_context != SearchContext.StaticClass) { var name = UISyntaxHighlight.ParseFullSyntax(obj.GetActualType(), true); if (unityObj && m_context != SearchContext.Singleton && m_context != SearchContext.StaticClass) { if (unityObj && !string.IsNullOrEmpty(unityObj.name)) name += $": {unityObj.name}"; else name += ": untitled"; } text.text = name; } else { var type = obj as Type; text.text = UISyntaxHighlight.ParseFullSyntax(type, true); } var label = text.transform.parent.parent.gameObject; if (!label.activeSelf) label.SetActive(true); } } m_lastCount = newCount; } // scene dropdown update internal bool HaveScenesChanged() { if (m_lastSceneCount != SceneManager.sceneCount) return true; for (int i = 0; i < SceneManager.sceneCount; i++) { int dropdownIndex = i + 3; if (dropdownIndex >= m_sceneDropdown.options.Count || m_sceneDropdown.options[dropdownIndex].text != SceneManager.GetSceneAt(i).name) return true; } return false; } internal void RefreshSceneDropdown() { m_sceneDropdown.OnCancel(null); m_sceneDropdown.options.Clear(); m_sceneDropdown.options.Add(new Dropdown.OptionData { text = "Any" }); m_sceneDropdown.options.Add(new Dropdown.OptionData { text = "None (Asset / Resource)" }); m_sceneDropdown.options.Add(new Dropdown.OptionData { text = "DontDestroyOnLoad" }); m_lastSceneCount = 0; for (int i = 0; i < SceneManager.sceneCount; i++) { m_lastSceneCount++; var scene = SceneManager.GetSceneAt(i).name; m_sceneDropdown.options.Add(new Dropdown.OptionData { text = scene }); } m_sceneDropdown.transform.Find("Label").GetComponent().text = "Any"; } // ~~~~~ UI Callbacks ~~~~~ internal void OnSearchClicked() { m_resultListPageHandler.CurrentPage = 0; if (m_context == SearchContext.StaticClass) StaticClassSearch(); else if (m_context == SearchContext.Singleton) SingletonSearch(); else UnityObjectSearch(); RefreshResultList(); if (m_results.Length > 0) m_resultCountText.text = $"{m_results.Length} Results"; else m_resultCountText.text = "No results..."; } internal void StaticClassSearch() { m_isStaticClassSearching = true; var list = new List(); var nameFilter = ""; if (!string.IsNullOrEmpty(m_nameInput.text)) nameFilter = m_nameInput.text.ToLower(); foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { foreach (var type in asm.TryGetTypes().Where(it => it.IsSealed && it.IsAbstract)) { if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ToLower().Contains(nameFilter)) continue; list.Add(type); } } m_results = list.ToArray(); } internal string[] s_instanceNames = new string[] { "m_instance", "m_Instance", "s_instance", "s_Instance", "_instance", "_Instance", "instance", "Instance", "k__BackingField", "k__BackingField", }; private void SingletonSearch() { m_isStaticClassSearching = false; var instances = new List(); var nameFilter = ""; if (!string.IsNullOrEmpty(m_nameInput.text)) nameFilter = m_nameInput.text.ToLower(); var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; foreach (var asm in AppDomain.CurrentDomain.GetAssemblies()) { // All non-static classes foreach (var type in asm.TryGetTypes().Where(it => !it.IsSealed && !it.IsAbstract)) { try { if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ToLower().Contains(nameFilter)) continue; #if CPP // Only look for Properties in IL2CPP, not for Mono. PropertyInfo pi; foreach (var name in s_instanceNames) { pi = type.GetProperty(name, flags); if (pi != null) { var instance = pi.GetValue(null, null); if (instance != null) { instances.Add(instance); continue; } } } #endif // Look for a typical Instance backing field. FieldInfo fi; foreach (var name in s_instanceNames) { fi = type.GetField(name, flags); if (fi != null) { var instance = fi.GetValue(null); if (instance != null) { instances.Add(instance); continue; } } } } catch { } } } m_results = instances.ToArray(); } internal void UnityObjectSearch() { m_isStaticClassSearching = false; Type searchType = null; switch (m_context) { case SearchContext.GameObject: searchType = typeof(GameObject); break; case SearchContext.Component: searchType = typeof(Component); break; case SearchContext.Custom: if (string.IsNullOrEmpty(m_customTypeInput.text)) { ExplorerCore.LogWarning("Custom Type input must not be empty!"); return; } if (ReflectionHelpers.GetTypeByName(m_customTypeInput.text) is Type customType) if (typeof(UnityEngine.Object).IsAssignableFrom(customType)) searchType = customType; else ExplorerCore.LogWarning($"Custom type '{customType.FullName}' is not assignable from UnityEngine.Object!"); else ExplorerCore.LogWarning($"Could not find a type by the name '{m_customTypeInput.text}'!"); break; default: searchType = typeof(UnityEngine.Object); break; } if (searchType == null) return; var allObjects = ResourcesUnstrip.FindObjectsOfTypeAll(searchType); var results = new List(); // perform filter comparers string nameFilter = null; if (!string.IsNullOrEmpty(m_nameInput.text)) nameFilter = m_nameInput.text.ToLower(); bool canGetGameObject = (m_sceneFilter != SceneFilter.Any || m_childFilter != ChildFilter.Any) && (m_context == SearchContext.GameObject || typeof(Component).IsAssignableFrom(searchType)); string sceneFilter = null; if (!canGetGameObject) { if (m_context != SearchContext.UnityObject && (m_sceneFilter != SceneFilter.Any || m_childFilter != ChildFilter.Any)) ExplorerCore.LogWarning($"Type '{searchType}' cannot have Scene or Child filters applied to it"); } else { if (m_sceneFilter == SceneFilter.DontDestroyOnLoad) sceneFilter = "DontDestroyOnLoad"; else if (m_sceneFilter == SceneFilter.Explicit) sceneFilter = m_sceneDropdown.options[m_sceneDropdown.value].text; } foreach (var obj in allObjects) { // name check if (!string.IsNullOrEmpty(nameFilter) && !obj.name.ToLower().Contains(nameFilter)) continue; if (canGetGameObject) { #if MONO var go = m_context == SearchContext.GameObject ? obj as GameObject : (obj as Component).gameObject; #else var go = m_context == SearchContext.GameObject ? obj.TryCast() : obj.TryCast().gameObject; #endif // scene check if (m_sceneFilter != SceneFilter.Any) { if (!go) continue; switch (m_context) { case SearchContext.GameObject: if (go.scene.name != sceneFilter) continue; break; case SearchContext.Custom: case SearchContext.Component: if (go.scene.name != sceneFilter) continue; break; } } if (m_childFilter != ChildFilter.Any) { if (!go) continue; // root object check (no parent) if (m_childFilter == ChildFilter.HasParent && !go.transform.parent) continue; else if (m_childFilter == ChildFilter.RootObject && go.transform.parent) continue; } } results.Add(obj); } m_results = results.ToArray(); } private void OnResultPageTurn() { RefreshResultList(); } internal void OnResultClicked(int index) { if (m_context == SearchContext.StaticClass) InspectorManager.Instance.Inspect((Type)m_resultShortList[index]); else InspectorManager.Instance.Inspect(m_resultShortList[index]); } internal void OnContextButtonClicked(SearchContext context) { if (m_selectedContextButton && m_context == context) return; if (m_selectedContextButton) UIFactory.SetDefaultColorTransitionValues(m_selectedContextButton); var button = m_contextButtons[context]; m_selectedContextButton = button; var colors = m_selectedContextButton.colors; colors.normalColor = new Color(0.35f, 0.7f, 0.35f); colors.highlightedColor = colors.normalColor; m_selectedContextButton.colors = colors; m_context = context; // if extra filters are valid if (context == SearchContext.Component || context == SearchContext.GameObject || context == SearchContext.Custom) { m_extraFilterRow?.SetActive(true); } else { m_extraFilterRow?.SetActive(false); } } #region UI CONSTRUCTION internal void ConstructUI() { GameObject parent = MainMenu.Instance.PageViewport; Content = UIFactory.CreateVerticalGroup(parent); var mainGroup = Content.GetComponent(); mainGroup.padding.left = 4; mainGroup.padding.right = 4; mainGroup.padding.top = 4; mainGroup.padding.bottom = 4; mainGroup.spacing = 5; mainGroup.childForceExpandHeight = true; mainGroup.childForceExpandWidth = true; mainGroup.childControlHeight = true; mainGroup.childControlWidth = true; ConstructTopArea(); ConstructResultsArea(); } internal void ConstructTopArea() { var topAreaObj = UIFactory.CreateVerticalGroup(Content, new Color(0.15f, 0.15f, 0.15f)); var topGroup = topAreaObj.GetComponent(); topGroup.childForceExpandHeight = false; topGroup.childControlHeight = true; topGroup.childForceExpandWidth = true; topGroup.childControlWidth = true; topGroup.padding.top = 5; topGroup.padding.left = 5; topGroup.padding.right = 5; topGroup.padding.bottom = 5; topGroup.spacing = 5; GameObject titleObj = UIFactory.CreateLabel(topAreaObj, TextAnchor.UpperLeft); Text titleLabel = titleObj.GetComponent(); titleLabel.text = "Search"; titleLabel.fontSize = 20; LayoutElement titleLayout = titleObj.AddComponent(); titleLayout.minHeight = 30; titleLayout.flexibleHeight = 0; // top area options var optionsGroupObj = UIFactory.CreateVerticalGroup(topAreaObj, new Color(0.1f, 0.1f, 0.1f)); var optionsGroup = optionsGroupObj.GetComponent(); optionsGroup.childForceExpandHeight = false; optionsGroup.childControlHeight = true; optionsGroup.childForceExpandWidth = true; optionsGroup.childControlWidth = true; optionsGroup.spacing = 10; optionsGroup.padding.top = 4; optionsGroup.padding.right = 4; optionsGroup.padding.left = 4; optionsGroup.padding.bottom = 4; var optionsLayout = optionsGroupObj.AddComponent(); optionsLayout.minWidth = 500; optionsLayout.minHeight = 70; optionsLayout.flexibleHeight = 100; // search context row var contextRowObj = UIFactory.CreateHorizontalGroup(optionsGroupObj, new Color(1, 1, 1, 0)); var contextGroup = contextRowObj.GetComponent(); contextGroup.childForceExpandWidth = false; contextGroup.childControlWidth = true; contextGroup.childForceExpandHeight = false; contextGroup.childControlHeight = true; contextGroup.spacing = 3; var contextLayout = contextRowObj.AddComponent(); contextLayout.minHeight = 25; var contextLabelObj = UIFactory.CreateLabel(contextRowObj, TextAnchor.MiddleLeft); var contextText = contextLabelObj.GetComponent(); contextText.text = "Searching for:"; var contextLabelLayout = contextLabelObj.AddComponent(); contextLabelLayout.minWidth = 125; contextLabelLayout.minHeight = 25; // context buttons AddContextButton(contextRowObj, "UnityEngine.Object", SearchContext.UnityObject, 140); AddContextButton(contextRowObj, "GameObject", SearchContext.GameObject); AddContextButton(contextRowObj, "Component", SearchContext.Component); AddContextButton(contextRowObj, "Custom...", SearchContext.Custom); // custom type input var customTypeObj = UIFactory.CreateInputField(contextRowObj); var customTypeLayout = customTypeObj.AddComponent(); customTypeLayout.minWidth = 250; customTypeLayout.flexibleWidth = 2000; customTypeLayout.minHeight = 25; customTypeLayout.flexibleHeight = 0; m_customTypeInput = customTypeObj.GetComponent(); m_customTypeInput.placeholder.gameObject.GetComponent().text = "eg. UnityEngine.Texture2D, etc..."; // static class and singleton buttons var secondRow = UIFactory.CreateHorizontalGroup(optionsGroupObj, new Color(1, 1, 1, 0)); var secondGroup = secondRow.GetComponent(); secondGroup.childForceExpandWidth = false; secondGroup.childForceExpandHeight = false; secondGroup.spacing = 3; var secondLayout = secondRow.AddComponent(); secondLayout.minHeight = 25; var spacer = UIFactory.CreateUIObject("spacer", secondRow); var spaceLayout = spacer.AddComponent(); spaceLayout.minWidth = 125; spaceLayout.minHeight = 25; AddContextButton(secondRow, "Static Class", SearchContext.StaticClass); AddContextButton(secondRow, "Singleton", SearchContext.Singleton); // search input var nameRowObj = UIFactory.CreateHorizontalGroup(optionsGroupObj, new Color(1, 1, 1, 0)); var nameRowGroup = nameRowObj.GetComponent(); nameRowGroup.childForceExpandWidth = true; nameRowGroup.childControlWidth = true; nameRowGroup.childForceExpandHeight = false; nameRowGroup.childControlHeight = true; var nameRowLayout = nameRowObj.AddComponent(); nameRowLayout.minHeight = 25; nameRowLayout.flexibleHeight = 0; nameRowLayout.flexibleWidth = 5000; var nameLabelObj = UIFactory.CreateLabel(nameRowObj, TextAnchor.MiddleLeft); var nameLabelText = nameLabelObj.GetComponent(); nameLabelText.text = "Name contains:"; var nameLabelLayout = nameLabelObj.AddComponent(); nameLabelLayout.minWidth = 125; nameLabelLayout.minHeight = 25; var nameInputObj = UIFactory.CreateInputField(nameRowObj); m_nameInput = nameInputObj.GetComponent(); //m_nameInput.placeholder.gameObject.GetComponent().text = ""; var nameInputLayout = nameInputObj.AddComponent(); nameInputLayout.minWidth = 150; nameInputLayout.flexibleWidth = 5000; nameInputLayout.minHeight = 25; // extra filter row m_extraFilterRow = UIFactory.CreateHorizontalGroup(optionsGroupObj, new Color(1, 1, 1, 0)); m_extraFilterRow.SetActive(false); var extraGroup = m_extraFilterRow.GetComponent(); extraGroup.childForceExpandHeight = true; extraGroup.childControlHeight = true; extraGroup.childForceExpandWidth = false; extraGroup.childControlWidth = true; var filterRowLayout = m_extraFilterRow.AddComponent(); filterRowLayout.minHeight = 25; filterRowLayout.flexibleHeight = 0; filterRowLayout.minWidth = 125; filterRowLayout.flexibleWidth = 150; // scene filter var sceneLabelObj = UIFactory.CreateLabel(m_extraFilterRow, TextAnchor.MiddleLeft); var sceneLabel = sceneLabelObj.GetComponent(); sceneLabel.text = "Scene Filter:"; var sceneLayout = sceneLabelObj.AddComponent(); sceneLayout.minWidth = 125; sceneLayout.minHeight = 25; var sceneDropObj = UIFactory.CreateDropdown(m_extraFilterRow, out m_sceneDropdown); m_sceneDropdown.itemText.text = "Any"; m_sceneDropdown.itemText.fontSize = 12; var sceneDropLayout = sceneDropObj.AddComponent(); sceneDropLayout.minWidth = 220; sceneDropLayout.minHeight = 25; m_sceneDropdown.onValueChanged.AddListener(OnSceneDropdownChanged); void OnSceneDropdownChanged(int value) { if (value < 4) m_sceneFilter = (SceneFilter)value; else m_sceneFilter = SceneFilter.Explicit; } // invisible space var invis = UIFactory.CreateUIObject("spacer", m_extraFilterRow); var invisLayout = invis.AddComponent(); invisLayout.minWidth = 25; invisLayout.flexibleWidth = 0; // children filter var childLabelObj = UIFactory.CreateLabel(m_extraFilterRow, TextAnchor.MiddleLeft); var childLabel = childLabelObj.GetComponent(); childLabel.text = "Child Filter:"; var childLayout = childLabelObj.AddComponent(); childLayout.minWidth = 100; childLayout.minHeight = 25; var childDropObj = UIFactory.CreateDropdown(m_extraFilterRow, out Dropdown childDrop); childDrop.itemText.text = "Any"; childDrop.itemText.fontSize = 12; var childDropLayout = childDropObj.AddComponent(); childDropLayout.minWidth = 180; childDropLayout.minHeight = 25; childDrop.options.Add(new Dropdown.OptionData { text = "Any" }); childDrop.options.Add(new Dropdown.OptionData { text = "Root Objects Only" }); childDrop.options.Add(new Dropdown.OptionData { text = "Children Only" }); childDrop.onValueChanged.AddListener(OnChildDropdownChanged); void OnChildDropdownChanged(int value) { m_childFilter = (ChildFilter)value; } // search button var searchBtnObj = UIFactory.CreateButton(topAreaObj); var searchText = searchBtnObj.GetComponentInChildren(); searchText.text = "Search"; LayoutElement searchBtnLayout = searchBtnObj.AddComponent(); searchBtnLayout.minHeight = 30; searchBtnLayout.flexibleHeight = 0; var searchBtn = searchBtnObj.GetComponent