mirror of
https://github.com/GrahamKracker/UnityExplorer.git
synced 2025-07-15 15:57:52 +08:00
2.0.0
lots, see release description
This commit is contained in:
17
src/UI/Main/BaseMainMenuPage.cs
Normal file
17
src/UI/Main/BaseMainMenuPage.cs
Normal file
@ -0,0 +1,17 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public abstract class BaseMainMenuPage
|
||||
{
|
||||
public virtual string Name { get; }
|
||||
|
||||
public Vector2 scroll = Vector2.zero;
|
||||
|
||||
public abstract void Init();
|
||||
|
||||
public abstract void DrawWindow();
|
||||
|
||||
public abstract void Update();
|
||||
}
|
||||
}
|
56
src/UI/Main/Console/AutoComplete.cs
Normal file
56
src/UI/Main/Console/AutoComplete.cs
Normal file
@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
// Thanks to ManlyMarco for this
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public struct AutoComplete
|
||||
{
|
||||
public string Full => Prefix + Addition;
|
||||
|
||||
public readonly string Prefix;
|
||||
public readonly string Addition;
|
||||
public readonly Contexts Context;
|
||||
|
||||
public Color TextColor => Context == Contexts.Namespace
|
||||
? Color.gray
|
||||
: Color.white;
|
||||
|
||||
public AutoComplete(string addition, string prefix, Contexts type)
|
||||
{
|
||||
Addition = addition;
|
||||
Prefix = prefix;
|
||||
Context = type;
|
||||
}
|
||||
|
||||
public enum Contexts
|
||||
{
|
||||
Namespace,
|
||||
Other
|
||||
}
|
||||
}
|
||||
|
||||
public static class AutoCompleteHelpers
|
||||
{
|
||||
public static HashSet<string> Namespaces => _namespaces ?? GetNamespaces();
|
||||
private static HashSet<string> _namespaces;
|
||||
|
||||
private static HashSet<string> GetNamespaces()
|
||||
{
|
||||
var set = new HashSet<string>(
|
||||
AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(GetTypes)
|
||||
.Where(x => x.IsPublic && !string.IsNullOrEmpty(x.Namespace))
|
||||
.Select(x => x.Namespace));
|
||||
|
||||
return _namespaces = set;
|
||||
|
||||
IEnumerable<Type> GetTypes(Assembly asm) => asm.TryGetTypes();
|
||||
}
|
||||
}
|
||||
}
|
70
src/UI/Main/Console/ScriptEvaluator.cs
Normal file
70
src/UI/Main/Console/ScriptEvaluator.cs
Normal file
@ -0,0 +1,70 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using Mono.CSharp;
|
||||
|
||||
// Thanks to ManlyMarco for this
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
internal class ScriptEvaluator : Evaluator, IDisposable
|
||||
{
|
||||
private static readonly HashSet<string> StdLib = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase)
|
||||
{
|
||||
"mscorlib", "System.Core", "System", "System.Xml"
|
||||
};
|
||||
|
||||
private readonly TextWriter _logger;
|
||||
|
||||
public ScriptEvaluator(TextWriter logger) : base(BuildContext(logger))
|
||||
{
|
||||
_logger = logger;
|
||||
|
||||
ImportAppdomainAssemblies(ReferenceAssembly);
|
||||
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad;
|
||||
_logger.Dispose();
|
||||
}
|
||||
|
||||
private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
|
||||
{
|
||||
string name = args.LoadedAssembly.GetName().Name;
|
||||
if (StdLib.Contains(name))
|
||||
return;
|
||||
ReferenceAssembly(args.LoadedAssembly);
|
||||
}
|
||||
|
||||
private static CompilerContext BuildContext(TextWriter tw)
|
||||
{
|
||||
var reporter = new StreamReportPrinter(tw);
|
||||
|
||||
var settings = new CompilerSettings
|
||||
{
|
||||
Version = LanguageVersion.Experimental,
|
||||
GenerateDebugInfo = false,
|
||||
StdLib = true,
|
||||
Target = Target.Library,
|
||||
WarningLevel = 0,
|
||||
EnhancedWarnings = false
|
||||
};
|
||||
|
||||
return new CompilerContext(settings, reporter);
|
||||
}
|
||||
|
||||
private static void ImportAppdomainAssemblies(Action<Assembly> import)
|
||||
{
|
||||
foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
string name = assembly.GetName().Name;
|
||||
if (StdLib.Contains(name))
|
||||
continue;
|
||||
import(assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
79
src/UI/Main/Console/ScriptInteraction.cs
Normal file
79
src/UI/Main/Console/ScriptInteraction.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Explorer.UI.Inspectors;
|
||||
using Mono.CSharp;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public class ScriptInteraction : InteractiveBase
|
||||
{
|
||||
public static void Log(object message)
|
||||
{
|
||||
ExplorerCore.Log(message);
|
||||
}
|
||||
|
||||
public static object CurrentTarget()
|
||||
{
|
||||
if (!WindowManager.TabView)
|
||||
{
|
||||
ExplorerCore.Log("CurrentTarget() is only a valid method when in Tab View mode!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return WindowManager.Windows.ElementAt(TabViewWindow.Instance.TargetTabID).Target;
|
||||
}
|
||||
|
||||
public static object[] AllTargets()
|
||||
{
|
||||
var list = new List<object>();
|
||||
foreach (var window in WindowManager.Windows)
|
||||
{
|
||||
if (window.Target != null)
|
||||
{
|
||||
list.Add(window.Target);
|
||||
}
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
public static void Inspect(object obj)
|
||||
{
|
||||
WindowManager.InspectObject(obj, out bool _);
|
||||
}
|
||||
|
||||
public static void Inspect(Type type)
|
||||
{
|
||||
WindowManager.InspectStaticReflection(type);
|
||||
}
|
||||
|
||||
public static void Help()
|
||||
{
|
||||
ExplorerCore.Log("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
|
||||
ExplorerCore.Log(" C# Console Help ");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("The following helper methods are available:");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("void Log(object message)");
|
||||
ExplorerCore.Log(" prints a message to the console window and debug log");
|
||||
ExplorerCore.Log(" usage: Log(\"hello world\");");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("object CurrentTarget()");
|
||||
ExplorerCore.Log(" returns the target object of the current tab (in tab view mode only)");
|
||||
ExplorerCore.Log(" usage: var target = CurrentTarget();");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("object[] AllTargets()");
|
||||
ExplorerCore.Log(" returns an object[] array containing all currently inspected objects");
|
||||
ExplorerCore.Log(" usage: var targets = AllTargets();");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("void Inspect(object obj)");
|
||||
ExplorerCore.Log(" inspects the provided object in a new window.");
|
||||
ExplorerCore.Log(" usage: Inspect(Camera.main);");
|
||||
ExplorerCore.Log("");
|
||||
ExplorerCore.Log("void Inspect(Type type)");
|
||||
ExplorerCore.Log(" attempts to inspect the provided type with static-only reflection.");
|
||||
ExplorerCore.Log(" usage: Inspect(typeof(Camera));");
|
||||
}
|
||||
}
|
||||
}
|
366
src/UI/Main/ConsolePage.cs
Normal file
366
src/UI/Main/ConsolePage.cs
Normal file
@ -0,0 +1,366 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Mono.CSharp;
|
||||
using System.Reflection;
|
||||
using System.IO;
|
||||
#if CPP
|
||||
using UnhollowerRuntimeLib;
|
||||
#endif
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public class ConsolePage : BaseMainMenuPage
|
||||
{
|
||||
public static ConsolePage Instance { get; private set; }
|
||||
|
||||
public override string Name { get => "C# Console"; }
|
||||
|
||||
private ScriptEvaluator m_evaluator;
|
||||
|
||||
public const string INPUT_CONTROL_NAME = "consoleInput";
|
||||
private string m_input = "";
|
||||
private string m_prevInput = "";
|
||||
private string m_usingInput = "";
|
||||
|
||||
public static List<AutoComplete> AutoCompletes = new List<AutoComplete>();
|
||||
public static List<string> UsingDirectives;
|
||||
|
||||
private Vector2 inputAreaScroll;
|
||||
private Vector2 autocompleteScroll;
|
||||
public static TextEditor textEditor;
|
||||
private bool shouldRefocus;
|
||||
|
||||
public static GUIStyle AutocompleteStyle => autocompleteStyle ?? GetCompletionStyle();
|
||||
private static GUIStyle autocompleteStyle;
|
||||
|
||||
public static readonly string[] DefaultUsing = new string[]
|
||||
{
|
||||
"System",
|
||||
"UnityEngine",
|
||||
"System.Linq",
|
||||
"System.Collections",
|
||||
"System.Collections.Generic",
|
||||
"System.Reflection"
|
||||
};
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
|
||||
try
|
||||
{
|
||||
m_input = @"// For a list of helper methods, execute the 'Help();' method.
|
||||
// Enable the Console Window with your Mod Loader to see log output.
|
||||
|
||||
Help();";
|
||||
ResetConsole();
|
||||
|
||||
foreach (var use in DefaultUsing)
|
||||
{
|
||||
AddUsing(use);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Error setting up console!\r\nMessage: {e.Message}");
|
||||
MainMenu.SetCurrentPage(0);
|
||||
MainMenu.Pages.Remove(this);
|
||||
}
|
||||
}
|
||||
|
||||
public override void Update() { }
|
||||
|
||||
public string AsmToUsing(string asm, bool richtext = false)
|
||||
{
|
||||
if (richtext)
|
||||
{
|
||||
return $"<color=#569cd6>using</color> {asm};";
|
||||
}
|
||||
return $"using {asm};";
|
||||
}
|
||||
|
||||
public void AddUsing(string asm)
|
||||
{
|
||||
if (!UsingDirectives.Contains(asm))
|
||||
{
|
||||
UsingDirectives.Add(asm);
|
||||
Evaluate(AsmToUsing(asm), true);
|
||||
}
|
||||
}
|
||||
|
||||
public object Evaluate(string str, bool suppressWarning = false)
|
||||
{
|
||||
object ret = VoidType.Value;
|
||||
|
||||
m_evaluator.Compile(str, out var compiled);
|
||||
|
||||
try
|
||||
{
|
||||
if (compiled == null)
|
||||
{
|
||||
throw new Exception("Mono.Csharp Service was unable to compile the code provided.");
|
||||
}
|
||||
|
||||
compiled.Invoke(ref ret);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
{
|
||||
ExplorerCore.LogWarning(e.GetType() + ", " + e.Message);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public void ResetConsole()
|
||||
{
|
||||
if (m_evaluator != null)
|
||||
{
|
||||
m_evaluator.Dispose();
|
||||
}
|
||||
|
||||
m_evaluator = new ScriptEvaluator(new StringWriter(new StringBuilder())) { InteractiveBaseClass = typeof(ScriptInteraction) };
|
||||
|
||||
UsingDirectives = new List<string>();
|
||||
}
|
||||
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
GUILayout.Label("<b><size=15><color=cyan>C# Console</color></size></b>", new GUILayoutOption[0]);
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
// SCRIPT INPUT
|
||||
|
||||
GUILayout.Label("Enter code here as though it is a method body:", new GUILayoutOption[0]);
|
||||
|
||||
inputAreaScroll = GUIUnstrip.BeginScrollView(
|
||||
inputAreaScroll,
|
||||
new GUILayoutOption[] { GUILayout.Height(250), GUILayout.ExpandHeight(true) }
|
||||
);
|
||||
|
||||
GUI.SetNextControlName(INPUT_CONTROL_NAME);
|
||||
m_input = GUIUnstrip.TextArea(m_input, new GUILayoutOption[] { GUILayout.ExpandHeight(true) });
|
||||
|
||||
GUIUnstrip.EndScrollView();
|
||||
|
||||
// EXECUTE BUTTON
|
||||
|
||||
if (GUILayout.Button("<color=cyan><b>Execute</b></color>", new GUILayoutOption[0]))
|
||||
{
|
||||
try
|
||||
{
|
||||
m_input = m_input.Trim();
|
||||
|
||||
if (!string.IsNullOrEmpty(m_input))
|
||||
{
|
||||
Evaluate(m_input);
|
||||
|
||||
//var result = Evaluate(m_input);
|
||||
|
||||
//if (result != null && !Equals(result, VoidType.Value))
|
||||
//{
|
||||
// ExplorerCore.Log("[Console Output]\r\n" + result.ToString());
|
||||
//}
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogError("Exception compiling!\r\nMessage: " + e.Message + "\r\nStack: " + e.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
// SUGGESTIONS
|
||||
if (AutoCompletes.Count > 0)
|
||||
{
|
||||
autocompleteScroll = GUIUnstrip.BeginScrollView(autocompleteScroll, new GUILayoutOption[] { GUILayout.Height(150) });
|
||||
|
||||
var origSkin = GUI.skin.button;
|
||||
GUI.skin.button = AutocompleteStyle;
|
||||
|
||||
foreach (var autocomplete in AutoCompletes)
|
||||
{
|
||||
AutocompleteStyle.normal.textColor = autocomplete.TextColor;
|
||||
if (GUILayout.Button(autocomplete.Full, new GUILayoutOption[] { GUILayout.Width(MainMenu.MainRect.width - 50) }))
|
||||
{
|
||||
UseAutocomplete(autocomplete.Addition);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GUI.skin.button = origSkin;
|
||||
|
||||
GUIUnstrip.EndScrollView();
|
||||
}
|
||||
|
||||
if (shouldRefocus)
|
||||
{
|
||||
GUI.FocusControl(INPUT_CONTROL_NAME);
|
||||
shouldRefocus = false;
|
||||
}
|
||||
|
||||
// USING DIRECTIVES
|
||||
|
||||
GUILayout.Label("<b>Using directives:</b>", new GUILayoutOption[0]);
|
||||
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("Add namespace:", new GUILayoutOption[] { GUILayout.Width(105) });
|
||||
m_usingInput = GUIUnstrip.TextField(m_usingInput, new GUILayoutOption[] { GUILayout.Width(150) });
|
||||
if (GUILayout.Button("<b><color=lime>Add</color></b>", new GUILayoutOption[] { GUILayout.Width(120) }))
|
||||
{
|
||||
AddUsing(m_usingInput);
|
||||
}
|
||||
if (GUILayout.Button("<b><color=red>Clear All</color></b>", new GUILayoutOption[] { GUILayout.Width(120) }))
|
||||
{
|
||||
ResetConsole();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
foreach (var asm in UsingDirectives)
|
||||
{
|
||||
GUILayout.Label(AsmToUsing(asm, true), new GUILayoutOption[0]);
|
||||
}
|
||||
|
||||
CheckAutocomplete();
|
||||
}
|
||||
|
||||
private void CheckAutocomplete()
|
||||
{
|
||||
// Temporary disabling this check in BepInEx Il2Cpp.
|
||||
#if BIE
|
||||
#if CPP
|
||||
#else
|
||||
if (GUI.GetNameOfFocusedControl() != INPUT_CONTROL_NAME)
|
||||
return;
|
||||
#endif
|
||||
#else
|
||||
if (GUI.GetNameOfFocusedControl() != INPUT_CONTROL_NAME)
|
||||
return;
|
||||
#endif
|
||||
|
||||
#if CPP
|
||||
textEditor = GUIUtility.GetStateObject(Il2CppType.Of<TextEditor>(), GUIUtility.keyboardControl).TryCast<TextEditor>();
|
||||
#else
|
||||
textEditor = (TextEditor)GUIUtility.GetStateObject(typeof(TextEditor), GUIUtility.keyboardControl);
|
||||
#endif
|
||||
|
||||
var input = m_input;
|
||||
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
try
|
||||
{
|
||||
var splitChars = new[] { ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&' };
|
||||
|
||||
// Credit ManlyMarco
|
||||
// Separate input into parts, grab only the part with cursor in it
|
||||
var cursorIndex = textEditor.cursorIndex;
|
||||
var start = cursorIndex <= 0 ? 0 : input.LastIndexOfAny(splitChars, cursorIndex - 1) + 1;
|
||||
var end = cursorIndex <= 0 ? input.Length : input.IndexOfAny(splitChars, cursorIndex - 1);
|
||||
if (end < 0 || end < start) end = input.Length;
|
||||
input = input.Substring(start, end - start);
|
||||
}
|
||||
catch (ArgumentException) { }
|
||||
|
||||
if (!string.IsNullOrEmpty(input) && input != m_prevInput)
|
||||
{
|
||||
GetAutocompletes(input);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ClearAutocompletes();
|
||||
}
|
||||
|
||||
m_prevInput = input;
|
||||
}
|
||||
|
||||
private void ClearAutocompletes()
|
||||
{
|
||||
if (AutoCompletes.Any())
|
||||
{
|
||||
AutoCompletes.Clear();
|
||||
shouldRefocus = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void UseAutocomplete(string suggestion)
|
||||
{
|
||||
int cursorIndex = textEditor.cursorIndex;
|
||||
m_input = m_input.Insert(cursorIndex, suggestion);
|
||||
|
||||
ClearAutocompletes();
|
||||
shouldRefocus = true;
|
||||
}
|
||||
|
||||
private void GetAutocompletes(string input)
|
||||
{
|
||||
try
|
||||
{
|
||||
//ExplorerCore.Log("Fetching suggestions for input " + input);
|
||||
|
||||
// Credit ManylMarco
|
||||
AutoCompletes.Clear();
|
||||
var completions = m_evaluator.GetCompletions(input, out string prefix);
|
||||
if (completions != null)
|
||||
{
|
||||
if (prefix == null)
|
||||
prefix = input;
|
||||
|
||||
AutoCompletes.AddRange(completions
|
||||
.Where(x => !string.IsNullOrEmpty(x))
|
||||
.Select(x => new AutoComplete(x, prefix, AutoComplete.Contexts.Other))
|
||||
);
|
||||
}
|
||||
|
||||
var trimmed = input.Trim();
|
||||
if (trimmed.StartsWith("using"))
|
||||
trimmed = trimmed.Remove(0, 5).Trim();
|
||||
|
||||
var namespaces = AutoCompleteHelpers.Namespaces
|
||||
.Where(x => x.StartsWith(trimmed) && x.Length > trimmed.Length)
|
||||
.Select(x => new AutoComplete(
|
||||
x.Substring(trimmed.Length),
|
||||
x.Substring(0, trimmed.Length),
|
||||
AutoComplete.Contexts.Namespace));
|
||||
|
||||
AutoCompletes.AddRange(namespaces);
|
||||
|
||||
shouldRefocus = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log("C# Console error:\r\n" + ex);
|
||||
ClearAutocompletes();
|
||||
}
|
||||
}
|
||||
|
||||
// Credit ManlyMarco
|
||||
private static GUIStyle GetCompletionStyle()
|
||||
{
|
||||
return autocompleteStyle = new GUIStyle(GUI.skin.button)
|
||||
{
|
||||
border = new RectOffset(0, 0, 0, 0),
|
||||
margin = new RectOffset(0, 0, 0, 0),
|
||||
padding = new RectOffset(0, 0, 0, 0),
|
||||
hover = { background = Texture2D.whiteTexture, textColor = Color.black },
|
||||
normal = { background = null },
|
||||
focused = { background = Texture2D.whiteTexture, textColor = Color.black },
|
||||
active = { background = Texture2D.whiteTexture, textColor = Color.black },
|
||||
alignment = TextAnchor.MiddleLeft,
|
||||
};
|
||||
}
|
||||
|
||||
private class VoidType
|
||||
{
|
||||
public static readonly VoidType Value = new VoidType();
|
||||
private VoidType() { }
|
||||
}
|
||||
}
|
||||
}
|
125
src/UI/Main/OptionsPage.cs
Normal file
125
src/UI/Main/OptionsPage.cs
Normal file
@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.Config;
|
||||
using Explorer.CacheObject;
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public class OptionsPage : BaseMainMenuPage
|
||||
{
|
||||
public override string Name => "Options";
|
||||
|
||||
public string toggleKeyInputString = "";
|
||||
public Vector2 defaultSizeInputVector;
|
||||
public int defaultPageLimit;
|
||||
public bool bitwiseSupport;
|
||||
public bool tabView;
|
||||
|
||||
private CacheObjectBase toggleKeyInput;
|
||||
private CacheObjectBase defaultSizeInput;
|
||||
private CacheObjectBase defaultPageLimitInput;
|
||||
private CacheObjectBase bitwiseSupportInput;
|
||||
private CacheObjectBase tabViewInput;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
toggleKeyInputString = ModConfig.Instance.Main_Menu_Toggle.ToString();
|
||||
toggleKeyInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("toggleKeyInputString"), this);
|
||||
|
||||
defaultSizeInputVector = ModConfig.Instance.Default_Window_Size;
|
||||
defaultSizeInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("defaultSizeInputVector"), this);
|
||||
|
||||
defaultPageLimit = ModConfig.Instance.Default_Page_Limit;
|
||||
defaultPageLimitInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("defaultPageLimit"), this);
|
||||
|
||||
bitwiseSupport = ModConfig.Instance.Bitwise_Support;
|
||||
bitwiseSupportInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("bitwiseSupport"), this);
|
||||
|
||||
tabView = ModConfig.Instance.Tab_View;
|
||||
tabViewInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("tabView"), this);
|
||||
}
|
||||
|
||||
public override void Update() { }
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label("<color=orange><size=16><b>Options</b></size></color>", new GUILayoutOption[0]);
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
|
||||
|
||||
GUIUnstrip.BeginVertical(GUIContent.none, GUI.skin.box, new GUILayoutOption[0]);
|
||||
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"Menu Toggle Key:", new GUILayoutOption[] { GUILayout.Width(215f) });
|
||||
toggleKeyInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"Default Window Size:", new GUILayoutOption[] { GUILayout.Width(215f) });
|
||||
defaultSizeInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"Default Items per Page:", new GUILayoutOption[] { GUILayout.Width(215f) });
|
||||
defaultPageLimitInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"Enable Bitwise Editing:", new GUILayoutOption[] { GUILayout.Width(215f) });
|
||||
bitwiseSupportInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label($"Enable Tab View:", new GUILayoutOption[] { GUILayout.Width(215f) });
|
||||
tabViewInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (GUILayout.Button("<color=lime><b>Apply and Save</b></color>", new GUILayoutOption[0]))
|
||||
{
|
||||
ApplyAndSave();
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
|
||||
GUIUnstrip.Space(10f);
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label("<color=orange><size=16><b>Other</b></size></color>", new GUILayoutOption[0]);
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
|
||||
|
||||
GUIUnstrip.BeginVertical(GUIContent.none, GUI.skin.box, new GUILayoutOption[0]);
|
||||
|
||||
if (GUILayout.Button("Inspect Test Class", new GUILayoutOption[0]))
|
||||
{
|
||||
WindowManager.InspectObject(Tests.TestClass.Instance, out bool _);
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void ApplyAndSave()
|
||||
{
|
||||
if (Enum.Parse(typeof(KeyCode), toggleKeyInputString) is KeyCode key)
|
||||
{
|
||||
ModConfig.Instance.Main_Menu_Toggle = key;
|
||||
}
|
||||
else
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not parse '{toggleKeyInputString}' to KeyCode!");
|
||||
}
|
||||
|
||||
ModConfig.Instance.Default_Window_Size = defaultSizeInputVector;
|
||||
ModConfig.Instance.Default_Page_Limit = defaultPageLimit;
|
||||
ModConfig.Instance.Bitwise_Support = bitwiseSupport;
|
||||
|
||||
ModConfig.Instance.Tab_View = tabView;
|
||||
WindowManager.TabView = tabView;
|
||||
|
||||
ModConfig.SaveSettings();
|
||||
}
|
||||
}
|
||||
}
|
436
src/UI/Main/ScenePage.cs
Normal file
436
src/UI/Main/ScenePage.cs
Normal file
@ -0,0 +1,436 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public class ScenePage : BaseMainMenuPage
|
||||
{
|
||||
public static ScenePage Instance;
|
||||
|
||||
public override string Name { get => "Scenes"; }
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
private float m_timeOfLastUpdate = -1f;
|
||||
private const int PASSIVE_UPDATE_INTERVAL = 1;
|
||||
|
||||
private static bool m_getRootObjectsFailed;
|
||||
|
||||
private static string m_currentScene = "";
|
||||
|
||||
// gameobject list
|
||||
private Transform m_currentTransform;
|
||||
private readonly List<CacheObjectBase> m_objectList = new List<CacheObjectBase>();
|
||||
|
||||
// search bar
|
||||
private bool m_searching = false;
|
||||
private string m_searchInput = "";
|
||||
private List<CacheObjectBase> m_searchResults = new List<CacheObjectBase>();
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public void OnSceneChange()
|
||||
{
|
||||
m_currentScene = UnityHelpers.ActiveSceneName;
|
||||
SetTransformTarget(null);
|
||||
}
|
||||
|
||||
public void SetTransformTarget(Transform t)
|
||||
{
|
||||
m_currentTransform = t;
|
||||
|
||||
if (m_searching)
|
||||
CancelSearch();
|
||||
|
||||
Update_Impl(true);
|
||||
}
|
||||
|
||||
public void TraverseUp()
|
||||
{
|
||||
if (m_currentTransform.parent != null)
|
||||
{
|
||||
SetTransformTarget(m_currentTransform.parent);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetTransformTarget(null);
|
||||
}
|
||||
}
|
||||
|
||||
public void Search()
|
||||
{
|
||||
m_searchResults = SearchSceneObjects(m_searchInput);
|
||||
m_searching = true;
|
||||
Pages.ItemCount = m_searchResults.Count;
|
||||
}
|
||||
|
||||
public void CancelSearch()
|
||||
{
|
||||
m_searching = false;
|
||||
|
||||
if (m_getRootObjectsFailed && !m_currentTransform)
|
||||
{
|
||||
GetRootObjectsManual_Impl();
|
||||
}
|
||||
}
|
||||
|
||||
public List<CacheObjectBase> SearchSceneObjects(string _search)
|
||||
{
|
||||
var matches = new List<CacheObjectBase>();
|
||||
|
||||
foreach (var obj in Resources.FindObjectsOfTypeAll(ReflectionHelpers.GameObjectType))
|
||||
{
|
||||
#if CPP
|
||||
var go = obj.TryCast<GameObject>();
|
||||
#else
|
||||
var go = obj as GameObject;
|
||||
#endif
|
||||
if (go.name.ToLower().Contains(_search.ToLower()) && go.scene.name == m_currentScene)
|
||||
{
|
||||
matches.Add(CacheFactory.GetCacheObject(go));
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
if (m_searching) return;
|
||||
|
||||
if (Time.time - m_timeOfLastUpdate < PASSIVE_UPDATE_INTERVAL) return;
|
||||
m_timeOfLastUpdate = Time.time;
|
||||
|
||||
Update_Impl();
|
||||
}
|
||||
|
||||
private void Update_Impl(bool manual = false)
|
||||
{
|
||||
List<Transform> allTransforms = new List<Transform>();
|
||||
|
||||
// get current list of all transforms (either scene root or our current transform children)
|
||||
if (m_currentTransform)
|
||||
{
|
||||
for (int i = 0; i < m_currentTransform.childCount; i++)
|
||||
{
|
||||
allTransforms.Add(m_currentTransform.GetChild(i));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_getRootObjectsFailed)
|
||||
{
|
||||
try
|
||||
{
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(i);
|
||||
|
||||
if (scene.name == m_currentScene)
|
||||
{
|
||||
allTransforms.AddRange(scene.GetRootGameObjects()
|
||||
.Select(it => it.transform));
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
ExplorerCore.Log("Exception getting root scene objects, falling back to backup method...");
|
||||
|
||||
m_getRootObjectsFailed = true;
|
||||
allTransforms.AddRange(GetRootObjectsManual_Impl());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!manual)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
allTransforms.AddRange(GetRootObjectsManual_Impl());
|
||||
}
|
||||
}
|
||||
|
||||
Pages.ItemCount = allTransforms.Count;
|
||||
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
// sort by childcount
|
||||
allTransforms.Sort((a, b) => b.childCount.CompareTo(a.childCount));
|
||||
|
||||
m_objectList.Clear();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < Pages.ItemCount; i++)
|
||||
{
|
||||
var child = allTransforms[i];
|
||||
m_objectList.Add(CacheFactory.GetCacheObject(child));
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable<Transform> GetRootObjectsManual_Impl()
|
||||
{
|
||||
try
|
||||
{
|
||||
var array = Resources.FindObjectsOfTypeAll(ReflectionHelpers.TransformType);
|
||||
|
||||
var list = new List<Transform>();
|
||||
foreach (var obj in array)
|
||||
{
|
||||
#if CPP
|
||||
var transform = obj.TryCast<Transform>();
|
||||
#else
|
||||
var transform = obj as Transform;
|
||||
#endif
|
||||
if (transform.parent == null && transform.gameObject.scene.name == m_currentScene)
|
||||
{
|
||||
list.Add(transform);
|
||||
}
|
||||
}
|
||||
return list;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log("Exception getting root scene objects (manual): "
|
||||
+ e.GetType() + ", " + e.Message + "\r\n"
|
||||
+ e.StackTrace);
|
||||
return new Transform[0];
|
||||
}
|
||||
}
|
||||
|
||||
// --------- GUI Draw Function --------- //
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
try
|
||||
{
|
||||
DrawHeaderArea();
|
||||
|
||||
GUIUnstrip.BeginVertical(GUIContent.none, GUI.skin.box, null);
|
||||
|
||||
DrawPageButtons();
|
||||
|
||||
if (!m_searching)
|
||||
{
|
||||
DrawGameObjectList();
|
||||
}
|
||||
else
|
||||
{
|
||||
DrawSearchResultsList();
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!e.Message.Contains("in a group with only"))
|
||||
{
|
||||
ExplorerCore.Log(e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawHeaderArea()
|
||||
{
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
// Current Scene label
|
||||
GUILayout.Label("Current Scene:", new GUILayoutOption[] { GUILayout.Width(120) });
|
||||
SceneChangeButtons();
|
||||
GUILayout.Label("<color=cyan>" + m_currentScene + "</color>", new GUILayoutOption[0]);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// ----- GameObject Search -----
|
||||
GUIUnstrip.BeginHorizontal(GUIContent.none, GUI.skin.box, null);
|
||||
GUILayout.Label("<b>Search Scene:</b>", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
|
||||
m_searchInput = GUIUnstrip.TextField(m_searchInput, new GUILayoutOption[0]);
|
||||
|
||||
if (GUILayout.Button("Search", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Search();
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIUnstrip.Space(5);
|
||||
}
|
||||
|
||||
private void SceneChangeButtons()
|
||||
{
|
||||
var scenes = new List<Scene>();
|
||||
var names = new List<string>();
|
||||
for (int i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(i);
|
||||
names.Add(scene.name);
|
||||
scenes.Add(scene);
|
||||
}
|
||||
|
||||
if (scenes.Count > 1)
|
||||
{
|
||||
int changeWanted = 0;
|
||||
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = -1;
|
||||
}
|
||||
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
|
||||
{
|
||||
changeWanted = 1;
|
||||
}
|
||||
if (changeWanted != 0)
|
||||
{
|
||||
int index = names.IndexOf(m_currentScene);
|
||||
index += changeWanted;
|
||||
if (index > scenes.Count - 1)
|
||||
{
|
||||
index = 0;
|
||||
}
|
||||
else if (index < 0)
|
||||
{
|
||||
index = scenes.Count - 1;
|
||||
}
|
||||
m_currentScene = scenes[index].name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawPageButtons()
|
||||
{
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
if (Pages.ItemCount > Pages.ItemsPerPage)
|
||||
{
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Left, ref this.scroll);
|
||||
|
||||
Update_Impl(true);
|
||||
}
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
|
||||
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Right, ref this.scroll);
|
||||
|
||||
Update_Impl(true);
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
}
|
||||
|
||||
private void DrawGameObjectList()
|
||||
{
|
||||
if (m_currentTransform != null)
|
||||
{
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
if (GUILayout.Button("<-", new GUILayoutOption[] { GUILayout.Width(35) }))
|
||||
{
|
||||
TraverseUp();
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<color=cyan>" + m_currentTransform.GetGameObjectPath() + "</color>",
|
||||
new GUILayoutOption[] { GUILayout.Width(MainMenu.MainRect.width - 187f) });
|
||||
}
|
||||
|
||||
Buttons.InspectButton(m_currentTransform);
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("Scene Root GameObjects:", new GUILayoutOption[0]);
|
||||
|
||||
if (m_getRootObjectsFailed)
|
||||
{
|
||||
if (GUILayout.Button("Update Root Object List (auto-update failed!)", new GUILayoutOption[0]))
|
||||
{
|
||||
Update_Impl(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_objectList.Count > 0)
|
||||
{
|
||||
for (int i = 0; i < m_objectList.Count; i++)
|
||||
{
|
||||
var obj = m_objectList[i];
|
||||
|
||||
if (obj == null) continue;
|
||||
|
||||
try
|
||||
{
|
||||
var go = obj.IValue.Value as GameObject ?? (obj.IValue.Value as Transform)?.gameObject;
|
||||
|
||||
if (!go)
|
||||
{
|
||||
string label = "<color=red><i>null";
|
||||
|
||||
if (go != null)
|
||||
{
|
||||
label += " (Destroyed)";
|
||||
}
|
||||
|
||||
label += "</i></color>";
|
||||
GUILayout.Label(label, new GUILayoutOption[0]);
|
||||
}
|
||||
else
|
||||
{
|
||||
Buttons.GameObjectButton(go, SetTransformTarget, true, MainMenu.MainRect.width - 170f);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSearchResultsList()
|
||||
{
|
||||
if (GUILayout.Button("<- Cancel Search", new GUILayoutOption[] { GUILayout.Width(150) }))
|
||||
{
|
||||
CancelSearch();
|
||||
}
|
||||
|
||||
GUILayout.Label("Search Results:", new GUILayoutOption[0]);
|
||||
|
||||
if (m_searchResults.Count > 0)
|
||||
{
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < m_searchResults.Count; i++)
|
||||
{
|
||||
var obj = m_searchResults[i].IValue.Value as GameObject;
|
||||
|
||||
if (obj)
|
||||
{
|
||||
Buttons.GameObjectButton(obj, SetTransformTarget, true, MainMenu.MainRect.width - 170);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<i><color=red>Null or destroyed!</color></i>", new GUILayoutOption[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<color=red><i>No results found!</i></color>", new GUILayoutOption[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
501
src/UI/Main/SearchPage.cs
Normal file
501
src/UI/Main/SearchPage.cs
Normal file
@ -0,0 +1,501 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using Explorer.UI.Shared;
|
||||
using Explorer.CacheObject;
|
||||
|
||||
namespace Explorer.UI.Main
|
||||
{
|
||||
public class SearchPage : BaseMainMenuPage
|
||||
{
|
||||
public static SearchPage Instance;
|
||||
|
||||
public override string Name { get => "Search"; }
|
||||
|
||||
private string m_searchInput = "";
|
||||
private string m_typeInput = "";
|
||||
|
||||
private Vector2 resultsScroll = Vector2.zero;
|
||||
|
||||
public PageHelper Pages = new PageHelper();
|
||||
|
||||
private List<CacheObjectBase> m_searchResults = new List<CacheObjectBase>();
|
||||
|
||||
public SceneFilter SceneMode = SceneFilter.Any;
|
||||
public TypeFilter TypeMode = TypeFilter.Object;
|
||||
|
||||
public enum SceneFilter
|
||||
{
|
||||
Any,
|
||||
This,
|
||||
DontDestroy,
|
||||
None
|
||||
}
|
||||
|
||||
public enum TypeFilter
|
||||
{
|
||||
Object,
|
||||
GameObject,
|
||||
Component,
|
||||
Custom
|
||||
}
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public void OnSceneChange()
|
||||
{
|
||||
m_searchResults.Clear();
|
||||
Pages.PageOffset = 0;
|
||||
}
|
||||
|
||||
public override void Update() { }
|
||||
|
||||
private void CacheResults(IEnumerable results, bool isStaticClasses = false)
|
||||
{
|
||||
m_searchResults = new List<CacheObjectBase>();
|
||||
|
||||
foreach (var obj in results)
|
||||
{
|
||||
var toCache = obj;
|
||||
|
||||
#if CPP
|
||||
if (toCache is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
toCache = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Transform>()?.gameObject ?? ilObject;
|
||||
}
|
||||
#else
|
||||
if (toCache is GameObject || toCache is Transform)
|
||||
{
|
||||
toCache = toCache as GameObject ?? (toCache as Transform).gameObject;
|
||||
}
|
||||
#endif
|
||||
|
||||
var cache = CacheFactory.GetCacheObject(toCache);
|
||||
cache.IsStaticClassSearchResult = isStaticClasses;
|
||||
m_searchResults.Add(cache);
|
||||
}
|
||||
|
||||
Pages.ItemCount = m_searchResults.Count;
|
||||
Pages.PageOffset = 0;
|
||||
}
|
||||
|
||||
public override void DrawWindow()
|
||||
{
|
||||
try
|
||||
{
|
||||
// helpers
|
||||
GUIUnstrip.BeginHorizontal(GUIContent.none, GUI.skin.box, null);
|
||||
GUILayout.Label("<b><color=orange>Helpers</color></b>", new GUILayoutOption[] { GUILayout.Width(70) });
|
||||
if (GUILayout.Button("Find Static Instances", new GUILayoutOption[] { GUILayout.Width(180) }))
|
||||
{
|
||||
CacheResults(GetStaticInstances());
|
||||
}
|
||||
if (GUILayout.Button("Find Static Classes", new GUILayoutOption[] { GUILayout.Width(180) }))
|
||||
{
|
||||
CacheResults(GetStaticClasses(), true);
|
||||
}
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
// search box
|
||||
SearchBox();
|
||||
|
||||
// results
|
||||
GUIUnstrip.BeginVertical(GUIContent.none, GUI.skin.box, null);
|
||||
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label("<b><color=orange>Results </color></b>" + " (" + m_searchResults.Count + ")", new GUILayoutOption[0]);
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
int count = m_searchResults.Count;
|
||||
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
Pages.DrawLimitInputArea();
|
||||
|
||||
if (count > Pages.ItemsPerPage)
|
||||
{
|
||||
// prev/next page buttons
|
||||
|
||||
if (Pages.ItemCount > Pages.ItemsPerPage)
|
||||
{
|
||||
if (GUILayout.Button("< Prev", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Left, ref this.resultsScroll);
|
||||
}
|
||||
|
||||
Pages.CurrentPageLabel();
|
||||
|
||||
if (GUILayout.Button("Next >", new GUILayoutOption[] { GUILayout.Width(80) }))
|
||||
{
|
||||
Pages.TurnPage(Turn.Right, ref this.resultsScroll);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
resultsScroll = GUIUnstrip.BeginScrollView(resultsScroll);
|
||||
|
||||
var _temprect = new Rect(MainMenu.MainRect.x, MainMenu.MainRect.y, MainMenu.MainRect.width + 160, MainMenu.MainRect.height);
|
||||
|
||||
if (m_searchResults.Count > 0)
|
||||
{
|
||||
int offset = Pages.CalculateOffsetIndex();
|
||||
|
||||
for (int i = offset; i < offset + Pages.ItemsPerPage && i < count; i++)
|
||||
{
|
||||
m_searchResults[i].Draw(MainMenu.MainRect, 0f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
GUILayout.Label("<color=red><i>No results found!</i></color>", new GUILayoutOption[0]);
|
||||
}
|
||||
|
||||
GUIUnstrip.EndScrollView();
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
catch
|
||||
{
|
||||
m_searchResults.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchBox()
|
||||
{
|
||||
GUIUnstrip.BeginVertical(GUIContent.none, GUI.skin.box, null);
|
||||
|
||||
// ----- GameObject Search -----
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
|
||||
GUILayout.Label("<b><color=orange>Search</color></b>", new GUILayoutOption[0]);
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
GUILayout.Label("Name Contains:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
m_searchInput = GUIUnstrip.TextField(m_searchInput, new GUILayoutOption[] { GUILayout.Width(200) });
|
||||
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
|
||||
GUILayout.Label("Class Filter:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
ClassFilterToggle(TypeFilter.Object, "Object");
|
||||
ClassFilterToggle(TypeFilter.GameObject, "GameObject");
|
||||
ClassFilterToggle(TypeFilter.Component, "Component");
|
||||
ClassFilterToggle(TypeFilter.Custom, "Custom");
|
||||
GUILayout.EndHorizontal();
|
||||
if (TypeMode == TypeFilter.Custom)
|
||||
{
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUI.skin.label.alignment = TextAnchor.MiddleRight;
|
||||
GUILayout.Label("Custom Class:", new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
GUI.skin.label.alignment = TextAnchor.UpperLeft;
|
||||
m_typeInput = GUIUnstrip.TextField(m_typeInput, new GUILayoutOption[] { GUILayout.Width(250) });
|
||||
GUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
|
||||
GUILayout.Label("Scene Filter:", new GUILayoutOption[] { GUILayout.Width(100) });
|
||||
SceneFilterToggle(SceneFilter.Any, "Any", 60);
|
||||
SceneFilterToggle(SceneFilter.This, "This Scene", 100);
|
||||
SceneFilterToggle(SceneFilter.DontDestroy, "DontDestroyOnLoad", 140);
|
||||
SceneFilterToggle(SceneFilter.None, "No Scene", 80);
|
||||
GUILayout.EndHorizontal();
|
||||
|
||||
if (GUILayout.Button("<b><color=cyan>Search</color></b>", new GUILayoutOption[0]))
|
||||
{
|
||||
Search();
|
||||
}
|
||||
|
||||
GUILayout.EndVertical();
|
||||
}
|
||||
|
||||
private void ClassFilterToggle(TypeFilter mode, string label)
|
||||
{
|
||||
if (TypeMode == mode)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(100) }))
|
||||
{
|
||||
TypeMode = mode;
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
private void SceneFilterToggle(SceneFilter mode, string label, float width)
|
||||
{
|
||||
if (SceneMode == mode)
|
||||
{
|
||||
GUI.color = Color.green;
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
if (GUILayout.Button(label, new GUILayoutOption[] { GUILayout.Width(width) }))
|
||||
{
|
||||
SceneMode = mode;
|
||||
}
|
||||
GUI.color = Color.white;
|
||||
}
|
||||
|
||||
|
||||
// -------------- ACTUAL METHODS (not Gui draw) ----------------- //
|
||||
|
||||
// ======= search functions =======
|
||||
|
||||
private void Search()
|
||||
{
|
||||
Pages.PageOffset = 0;
|
||||
CacheResults(FindAllObjectsOfType(m_searchInput, m_typeInput));
|
||||
}
|
||||
|
||||
private List<object> FindAllObjectsOfType(string searchQuery, string typeName)
|
||||
{
|
||||
#if CPP
|
||||
Il2CppSystem.Type searchType = null;
|
||||
|
||||
#else
|
||||
Type searchType = null;
|
||||
#endif
|
||||
if (TypeMode == TypeFilter.Custom)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ReflectionHelpers.GetTypeByName(typeName) is Type t)
|
||||
{
|
||||
#if CPP
|
||||
searchType = Il2CppSystem.Type.GetType(t.AssemblyQualifiedName);
|
||||
#else
|
||||
searchType = t;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new Exception($"Could not find a Type by the name of '{typeName}'!");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log("Exception getting Search Type: " + e.GetType() + ", " + e.Message);
|
||||
}
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Object)
|
||||
{
|
||||
searchType = ReflectionHelpers.ObjectType;
|
||||
}
|
||||
else if (TypeMode == TypeFilter.GameObject)
|
||||
{
|
||||
searchType = ReflectionHelpers.GameObjectType;
|
||||
}
|
||||
else if (TypeMode == TypeFilter.Component)
|
||||
{
|
||||
searchType = ReflectionHelpers.ComponentType;
|
||||
}
|
||||
|
||||
if (!ReflectionHelpers.ObjectType.IsAssignableFrom(searchType))
|
||||
{
|
||||
if (searchType != null)
|
||||
{
|
||||
ExplorerCore.LogWarning("Your Custom Class Type must inherit from UnityEngine.Object!");
|
||||
}
|
||||
return new List<object>();
|
||||
}
|
||||
|
||||
var matches = new List<object>();
|
||||
|
||||
var allObjectsOfType = Resources.FindObjectsOfTypeAll(searchType);
|
||||
|
||||
//ExplorerCore.Log("Found count: " + allObjectsOfType.Length);
|
||||
|
||||
int i = 0;
|
||||
foreach (var obj in allObjectsOfType)
|
||||
{
|
||||
if (i >= 2000) break;
|
||||
|
||||
if (searchQuery != "" && !obj.name.ToLower().Contains(searchQuery.ToLower()))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (searchType.FullName == ReflectionHelpers.ComponentType.FullName
|
||||
#if CPP
|
||||
&& ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetIl2CppType()))
|
||||
#else
|
||||
&& ReflectionHelpers.TransformType.IsAssignableFrom(obj.GetType()))
|
||||
#endif
|
||||
{
|
||||
// Transforms shouldn't really be counted as Components, skip them.
|
||||
// They're more akin to GameObjects.
|
||||
continue;
|
||||
}
|
||||
|
||||
if (SceneMode != SceneFilter.Any && !FilterScene(obj, this.SceneMode))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!matches.Contains(obj))
|
||||
{
|
||||
matches.Add(obj);
|
||||
}
|
||||
|
||||
i++;
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
|
||||
public static bool FilterScene(object obj, SceneFilter filter)
|
||||
{
|
||||
GameObject go = null;
|
||||
#if CPP
|
||||
if (obj is Il2CppSystem.Object ilObject)
|
||||
{
|
||||
go = ilObject.TryCast<GameObject>() ?? ilObject.TryCast<Component>().gameObject;
|
||||
}
|
||||
#else
|
||||
if (obj is GameObject || obj is Component)
|
||||
{
|
||||
go = (obj as GameObject) ?? (obj as Component).gameObject;
|
||||
}
|
||||
#endif
|
||||
|
||||
if (!go)
|
||||
{
|
||||
// object is not on a GameObject, cannot perform scene filter operation.
|
||||
return false;
|
||||
}
|
||||
|
||||
if (filter == SceneFilter.None)
|
||||
{
|
||||
return string.IsNullOrEmpty(go.scene.name);
|
||||
}
|
||||
else if (filter == SceneFilter.This)
|
||||
{
|
||||
return go.scene.name == UnityHelpers.ActiveSceneName;
|
||||
}
|
||||
else if (filter == SceneFilter.DontDestroy)
|
||||
{
|
||||
return go.scene.name == "DontDestroyOnLoad";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// ====== other ========
|
||||
|
||||
private static bool FilterName(string name)
|
||||
{
|
||||
// Don't really want these instances.
|
||||
return !name.StartsWith("Mono")
|
||||
&& !name.StartsWith("System")
|
||||
&& !name.StartsWith("Il2CppSystem")
|
||||
&& !name.StartsWith("Iced");
|
||||
}
|
||||
|
||||
// credit: ManlyMarco (RuntimeUnityEditor)
|
||||
public static IEnumerable<object> GetStaticInstances()
|
||||
{
|
||||
var query = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.SelectMany(t => t.TryGetTypes())
|
||||
.Where(t => t.IsClass && !t.IsAbstract && !t.ContainsGenericParameters);
|
||||
|
||||
var flags = BindingFlags.Public | BindingFlags.Static;
|
||||
var flatFlags = flags | BindingFlags.FlattenHierarchy;
|
||||
|
||||
foreach (var type in query)
|
||||
{
|
||||
object obj = null;
|
||||
try
|
||||
{
|
||||
var pi = type.GetProperty("Instance", flags);
|
||||
|
||||
if (pi == null)
|
||||
{
|
||||
pi = type.GetProperty("Instance", flatFlags);
|
||||
}
|
||||
|
||||
if (pi != null)
|
||||
{
|
||||
obj = pi.GetValue(null, null);
|
||||
}
|
||||
else
|
||||
{
|
||||
var fi = type.GetField("Instance", flags);
|
||||
|
||||
if (fi == null)
|
||||
{
|
||||
fi = type.GetField("Instance", flatFlags);
|
||||
}
|
||||
|
||||
if (fi != null)
|
||||
{
|
||||
obj = fi.GetValue(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
if (obj != null)
|
||||
{
|
||||
var t = ReflectionHelpers.GetActualType(obj);
|
||||
|
||||
if (!FilterName(t.FullName) || ReflectionHelpers.IsEnumerable(t))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
yield return obj;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private IEnumerable GetStaticClasses()
|
||||
{
|
||||
var list = new List<Type>();
|
||||
|
||||
var input = m_searchInput?.ToLower() ?? "";
|
||||
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
try
|
||||
{
|
||||
foreach (var type in asm.TryGetTypes())
|
||||
{
|
||||
if (!type.IsAbstract || !type.IsSealed)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrEmpty(input))
|
||||
{
|
||||
var typename = type.FullName.ToLower();
|
||||
|
||||
if (!typename.Contains(input))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
list.Add(type);
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user