diff --git a/README.md b/README.md
index c444e56..74b60dc 100644
--- a/README.md
+++ b/README.md
@@ -3,8 +3,9 @@
- An in-game explorer and a suite of debugging tools for IL2CPP and Mono Unity games, using MelonLoader and BepInEx .
-
+ An in-game explorer and a suite of debugging tools for IL2CPP and Mono Unity games, to aid with modding development.
+
+
@@ -22,20 +23,19 @@
## Releases
-| Mod Loader | Il2Cpp | Mono |
+| Mod Loader | IL2CPP | Mono |
| ----------- | ------ | ---- |
| [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) | ✔️ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.MelonLoader.Il2Cpp.zip) | ✔️ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.MelonLoader.Mono.zip) |
| [BepInEx](https://github.com/BepInEx/BepInEx) | ❔ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.BepInEx.Il2Cpp.zip) | ✔️ [link](https://github.com/sinai-dev/Explorer/releases/latest/download/Explorer.BepInEx.Mono.zip) |
-Il2Cpp Issues:
+IL2CPP Issues:
* Some methods may still fail with a `MissingMethodException`, please let me know if you experience this (with full debug log please).
-* Reflection may fail with certain types, see [here](https://github.com/knah/Il2CppAssemblyUnhollower#known-issues) for more details.
-* Scrolling with mouse wheel in the Explorer menu may not work on all games at the moment.
+* Reflection may fail with certain types, see [here](https://github.com/knah/IL2CPPAssemblyUnhollower#known-issues) for more details.
## Features
-
+
* Scene Explorer : Simple menu to traverse the Transform heirarchy of the scene.
@@ -47,79 +47,54 @@
## How to install
-### MelonLoader
-Requires [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) to be installed for your game.
-
-1. Download the relevant release from above.
-2. Unzip the file into the `Mods` folder in your game's installation directory, created by MelonLoader.
-3. Make sure it's not in a sub-folder, `Explorer.dll` should be directly in the `Mods\` folder.
-
### BepInEx
-Requires [BepInEx](https://github.com/BepInEx/BepInEx) to be installed for your game.
-1. Download the relevant release from above.
-2. Unzip the file into the `BepInEx\plugins\` folder in your game's installation directory, created by BepInEx.
-3. Make sure it's not in a sub-folder, `Explorer.dll` should be directly in the `plugins\` folder.
+0. Install [BepInEx](https://github.com/BepInEx/BepInEx) for your game.
+1. Download the UnityExplorer release for BepInEx IL2CPP or Mono above.
+2. Take the `UnityExplorer.dll` file and put it in `[GameFolder]\BepInEx\plugins\`
+3. Take the `UnityExplorer\` folder (with `explorerui.bundle`) and put it in `[GameFolder]\Mods\`, so it looks like `[GameFolder]\Mods\UnityExplorer\explorerui.bundle`.
+4. In IL2CPP, it is highly recommended to get the base Unity libs for the game's Unity version and put them in the `BepInEx\unhollowed\base\` folder.
+
+### MelonLoader
+
+0. Install [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) for your game.
+1. Download the UnityExplorer release for MelonLoader IL2CPP or Mono above.
+2. Take the contents of the release and put it in the `[GameFolder]\Mods\` folder. It should look like `[GameFolder]\Mods\UnityExplorer.dll` and `[GameFolder]\Mods\UnityExplorer\explorerui.bundle`.
## Mod Config
-There is a simple Mod Config for the Explorer. You can access the settings via the "Options" page of the main menu.
+You can access the settings via the "Options" page of the main menu, or directly from the config at `Mods\UnityExplorer\config.xml` (generated after first launch).
-`Main Menu Toggle` (KeyCode) | Default: `F7`
+`Main Menu Toggle` (KeyCode)
+* Default: `F7`
* See [this article](https://docs.unity3d.com/ScriptReference/KeyCode.html) for a full list of all accepted KeyCodes.
-`Default Window Size` (Vector2) | Default: `x: 550, y: 700`
-* Sets the default width and height for all Explorer windows when created.
+`Force Unlock Mouse` (bool)
+* Default: `true`
+* Forces the cursor to be unlocked and visible while the UnityExplorer menu is open, and prevents anything else taking control.
-`Default Items per Page` (int) | Default: `20`
+`Default Page Limit` (int)
+* Default: `25`
* Sets the default items per page when viewing lists or search results.
+* Requires a restart to take effect , apart from Reflection Inspector tabs.
-`Enable Bitwise Editing` (bool) | Default: `false`
-* Whether or not to show the Bitwise Editing helper when inspecting integers
-
-`Enable Tab View` (bool) | Default: `true`
-* Whether or not all inspector windows a grouped into a single window with tabs.
-
-`Default Output Path` (string) | Default: `Mods\Explorer`
+`Default Output Path` (string)
+* Default: `Mods\Explorer`
* Where output is generated to, by default (for Texture PNG saving, etc).
-## Mouse Control
-
-Explorer can force the mouse to be visible and unlocked when the menu is open, if you have enabled "Force Unlock Mouse" (Left-Alt toggle). Explorer also attempts to prevent clicking-through onto the game behind the Explorer menu.
-
-If you need more mouse control:
-
-* For VRChat, use [VRCExplorerMouseControl](https://github.com/sinai-dev/VRCExplorerMouseControl)
-* For Hellpoint, use [HPExplorerMouseControl](https://github.com/sinai-dev/Hellpoint-Mods/tree/master/HPExplorerMouseControl/HPExplorerMouseControl)
-* You can create your own plugin using one of the two plugins above as an example. Usually only a few simple Harmony patches are needed to fix the problem.
-
-For example:
-```csharp
-using Explorer;
-using Harmony; // or 'using HarmonyLib;' for BepInEx
-// ...
-// You will need to figure out the relevant Class and Method for your game using dnSpy.
-[HarmonyPatch(typeof(MyGame.InputManager), nameof(MyGame.InputManager.Update))]
-public class InputManager_Update
-{
- [HarmonyPrefix]
- public static bool Prefix()
- {
- // prevent method running if menu open, let it run if not.
- return !ExplorerCore.ShowMenu;
- }
-}
-```
+`Log Unity Debug` (bool)
+* Default: `false`
+* Listens for Unity `Debug.Log` messages and prints them to UnityExplorer's log.
## Building
-If you'd like to build this yourself, you will need to have installed BepInEx and/or MelonLoader for at least one Unity game. If you want to build all 4 versions, you will need at least one Il2Cpp and one Mono game, with BepInEx and MelonLoader installed for both.
+If you'd like to build this yourself, you will need to have installed BepInEx and/or MelonLoader for at least one Unity game. If you want to build all 4 versions, you will need at least one IL2CPP and one Mono game, with BepInEx and MelonLoader installed for both.
1. Install MelonLoader or BepInEx for your game.
2. Open the `src\Explorer.csproj` file in a text editor.
-3. Set the relevant `GameFolder` values for the versions you want to build, eg. set `MLCppGameFolder` if you want to build for a MelonLoader Il2Cpp game.
+3. Set the relevant `GameFolder` values for the versions you want to build, eg. set `MLCppGameFolder` if you want to build for a MelonLoader IL2CPP game.
4. Open the `src\Explorer.sln` project.
-5. Select `Solution 'Explorer' (1 of 1 project)` in the Solution Explorer panel, and set the Active config property to the version you want to build, then build it.
+5. Select `Solution 'UnityExplorer' (1 of 1 project)` in the Solution Explorer panel, and set the Active config property to the version you want to build, then build it.
5. The DLLs are built to the `Release\` folder in the root of the repository.
6. If ILRepack fails or is missing, use the NuGet package manager to re-install `ILRepack.Lib.MSBuild.Task`, then re-build.
@@ -128,5 +103,5 @@ If you'd like to build this yourself, you will need to have installed BepInEx an
Written by Sinai.
Thanks to:
-* [ManlyMarco](https://github.com/ManlyMarco) for their [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor), which I used for the REPL Console and the "Find instances" snippet, and the UI style.
-* [denikson](https://github.com/denikson) for [mcs-unity](https://github.com/denikson/mcs-unity). I commented out the `SkipVisibilityExt` constructor since it was causing an exception with the Hook it attempted.
+* [ManlyMarco](https://github.com/ManlyMarco) for their [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor), which I used for some aspects of the C# Console and Auto-Complete features.
+* [denikson](https://github.com/denikson) (aka Horse) for [mcs-unity](https://github.com/denikson/mcs-unity). I commented out the `SkipVisibilityExt` constructor since it was causing an exception with the Hook it attempted in IL2CPP.
diff --git a/icon.png b/icon.png
index ae791aa..edb16bc 100644
Binary files a/icon.png and b/icon.png differ
diff --git a/lib/UnityEngine.UI.dll b/lib/UnityEngine.UI.dll
new file mode 100644
index 0000000..aeee3d8
Binary files /dev/null and b/lib/UnityEngine.UI.dll differ
diff --git a/overview.png b/overview.png
index f099f68..6491f9a 100644
Binary files a/overview.png and b/overview.png differ
diff --git a/resources/explorerui.bundle b/resources/explorerui.bundle
new file mode 100644
index 0000000..c6acc12
Binary files /dev/null and b/resources/explorerui.bundle differ
diff --git a/src/CSConsole/AutoCompleter.cs b/src/CSConsole/AutoCompleter.cs
new file mode 100644
index 0000000..42dade8
--- /dev/null
+++ b/src/CSConsole/AutoCompleter.cs
@@ -0,0 +1,314 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using UnityEngine.EventSystems;
+using UnityEngine.UI;
+using UnityExplorer.Helpers;
+using UnityExplorer.UI;
+using UnityExplorer.UI.Modules;
+
+namespace UnityExplorer.CSConsole
+{
+ public class AutoCompleter
+ {
+ public static AutoCompleter Instance;
+
+ public const int MAX_LABELS = 500;
+ private const int UPDATES_PER_BATCH = 100;
+
+ public static GameObject m_mainObj;
+ //private static RectTransform m_thisRect;
+
+ private static readonly List m_suggestionButtons = new List();
+ private static readonly List m_suggestionTexts = new List();
+ private static readonly List m_hiddenSuggestionTexts = new List();
+
+ private static bool m_suggestionsDirty;
+ private static Suggestion[] m_suggestions = new Suggestion[0];
+ private static int m_lastBatchIndex;
+
+ private static string m_prevInput = "NULL";
+ private static int m_lastCaretPos;
+
+ public static void Init()
+ {
+ ConstructUI();
+
+ m_mainObj.SetActive(false);
+ }
+
+ public static void Update()
+ {
+ if (!m_mainObj)
+ {
+ return;
+ }
+
+ if (!CodeEditor.EnableAutocompletes)
+ {
+ if (m_mainObj.activeSelf)
+ {
+ m_mainObj.SetActive(false);
+ }
+
+ return;
+ }
+
+ RefreshButtons();
+
+ UpdatePosition();
+ }
+
+ public static void SetSuggestions(Suggestion[] suggestions)
+ {
+ m_suggestions = suggestions;
+
+ m_suggestionsDirty = true;
+ m_lastBatchIndex = 0;
+ }
+
+ private static void RefreshButtons()
+ {
+ if (!m_suggestionsDirty)
+ {
+ return;
+ }
+
+ if (m_suggestions.Length < 1)
+ {
+ if (m_mainObj.activeSelf)
+ {
+ m_mainObj?.SetActive(false);
+ }
+ return;
+ }
+
+ if (!m_mainObj.activeSelf)
+ {
+ m_mainObj.SetActive(true);
+ }
+
+ if (m_suggestions.Length < 1 || m_lastBatchIndex >= MAX_LABELS)
+ {
+ m_suggestionsDirty = false;
+ return;
+ }
+
+ int end = m_lastBatchIndex + UPDATES_PER_BATCH;
+ for (int i = m_lastBatchIndex; i < end && i < MAX_LABELS; i++)
+ {
+ if (i >= m_suggestions.Length)
+ {
+ if (m_suggestionButtons[i].activeSelf)
+ {
+ m_suggestionButtons[i].SetActive(false);
+ }
+ }
+ else
+ {
+ if (!m_suggestionButtons[i].activeSelf)
+ {
+ m_suggestionButtons[i].SetActive(true);
+ }
+
+ var suggestion = m_suggestions[i];
+ var label = m_suggestionTexts[i];
+ var hiddenLabel = m_hiddenSuggestionTexts[i];
+
+ label.text = suggestion.Full;
+ hiddenLabel.text = suggestion.Addition;
+
+ label.color = suggestion.TextColor;
+ }
+
+ m_lastBatchIndex = i;
+ }
+
+ m_lastBatchIndex++;
+ }
+
+ private static void UpdatePosition()
+ {
+ try
+ {
+ var editor = CSConsolePage.Instance.m_codeEditor;
+
+ if (!editor.InputField.isFocused)
+ return;
+
+ var textGen = editor.InputText.cachedTextGenerator;
+ int caretPos = editor.m_lastCaretPos;
+
+ if (caretPos == m_lastCaretPos)
+ return;
+
+ m_lastCaretPos = caretPos;
+
+ if (caretPos >= 1)
+ caretPos--;
+
+ var pos = textGen.characters[caretPos].cursorPos;
+
+ pos = editor.InputField.transform.TransformPoint(pos);
+
+ m_mainObj.transform.position = new Vector3(pos.x + 10, pos.y - 20, 0);
+ }
+ catch //(Exception e)
+ {
+ //ExplorerCore.Log(e.ToString());
+ }
+ }
+
+ private static readonly char[] splitChars = new[] { '{', '}', ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&', '?' };
+
+ public static void CheckAutocomplete()
+ {
+ var m_codeEditor = CSConsolePage.Instance.m_codeEditor;
+ string input = m_codeEditor.InputField.text;
+ int caretIndex = m_codeEditor.InputField.caretPosition;
+
+ if (!string.IsNullOrEmpty(input))
+ {
+ try
+ {
+ int start = caretIndex <= 0 ? 0 : input.LastIndexOfAny(splitChars, caretIndex - 1) + 1;
+ input = input.Substring(start, caretIndex - start).Trim();
+ }
+ catch (ArgumentException) { }
+ }
+
+ if (!string.IsNullOrEmpty(input) && input != m_prevInput)
+ {
+ GetAutocompletes(input);
+ }
+ else
+ {
+ ClearAutocompletes();
+ }
+
+ m_prevInput = input;
+ }
+
+ public static void ClearAutocompletes()
+ {
+ if (CodeEditor.AutoCompletes.Any())
+ {
+ CodeEditor.AutoCompletes.Clear();
+ }
+ }
+
+ public static void GetAutocompletes(string input)
+ {
+ try
+ {
+ // Credit ManylMarco
+ CodeEditor.AutoCompletes.Clear();
+ string[] completions = CSConsolePage.Instance.m_evaluator.GetCompletions(input, out string prefix);
+ if (completions != null)
+ {
+ if (prefix == null)
+ {
+ prefix = input;
+ }
+
+ CodeEditor.AutoCompletes.AddRange(completions
+ .Where(x => !string.IsNullOrEmpty(x))
+ .Select(x => new Suggestion(x, prefix, Suggestion.Contexts.Other))
+ );
+ }
+
+ string trimmed = input.Trim();
+ if (trimmed.StartsWith("using"))
+ {
+ trimmed = trimmed.Remove(0, 5).Trim();
+ }
+
+ IEnumerable namespaces = Suggestion.Namespaces
+ .Where(x => x.StartsWith(trimmed) && x.Length > trimmed.Length)
+ .Select(x => new Suggestion(
+ x.Substring(trimmed.Length),
+ x.Substring(0, trimmed.Length),
+ Suggestion.Contexts.Namespace));
+
+ CodeEditor.AutoCompletes.AddRange(namespaces);
+
+ IEnumerable keywords = Suggestion.Keywords
+ .Where(x => x.StartsWith(trimmed) && x.Length > trimmed.Length)
+ .Select(x => new Suggestion(
+ x.Substring(trimmed.Length),
+ x.Substring(0, trimmed.Length),
+ Suggestion.Contexts.Keyword));
+
+ CodeEditor.AutoCompletes.AddRange(keywords);
+ }
+ catch (Exception ex)
+ {
+ ExplorerCore.Log("Autocomplete error:\r\n" + ex.ToString());
+ ClearAutocompletes();
+ }
+ }
+
+ #region UI Construction
+
+ private static void ConstructUI()
+ {
+ var parent = UIManager.CanvasRoot;
+
+ var obj = UIFactory.CreateScrollView(parent, out GameObject content, out _, new Color(0.1f, 0.1f, 0.1f, 0.95f));
+
+ m_mainObj = obj;
+
+ var mainRect = obj.GetComponent();
+ //m_thisRect = mainRect;
+ mainRect.pivot = new Vector2(0f, 1f);
+ mainRect.anchorMin = new Vector2(0.45f, 0.45f);
+ mainRect.anchorMax = new Vector2(0.65f, 0.6f);
+ mainRect.offsetMin = Vector2.zero;
+ mainRect.offsetMax = Vector2.zero;
+
+ var mainGroup = content.GetComponent();
+ mainGroup.childControlHeight = false;
+ mainGroup.childControlWidth = true;
+ mainGroup.childForceExpandHeight = false;
+ mainGroup.childForceExpandWidth = true;
+
+ for (int i = 0; i < MAX_LABELS; i++)
+ {
+ var buttonObj = UIFactory.CreateButton(content);
+ Button btn = buttonObj.GetComponent();
+ ColorBlock btnColors = btn.colors;
+ btnColors.normalColor = new Color(0f, 0f, 0f, 0f);
+ btnColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1.0f);
+ btn.colors = btnColors;
+
+ var nav = btn.navigation;
+ nav.mode = Navigation.Mode.Vertical;
+ btn.navigation = nav;
+
+ var btnLayout = buttonObj.AddComponent();
+ btnLayout.minHeight = 20;
+
+ var text = btn.GetComponentInChildren();
+ text.alignment = TextAnchor.MiddleLeft;
+ text.color = Color.white;
+
+ var hiddenChild = UIFactory.CreateUIObject("HiddenText", buttonObj);
+ hiddenChild.SetActive(false);
+ var hiddenText = hiddenChild.AddComponent();
+ m_hiddenSuggestionTexts.Add(hiddenText);
+ btn.onClick.AddListener(UseAutocompleteButton);
+
+ void UseAutocompleteButton()
+ {
+ CSConsolePage.Instance.m_codeEditor.UseAutocomplete(hiddenText.text);
+ }
+
+ m_suggestionButtons.Add(buttonObj);
+ m_suggestionTexts.Add(text);
+ }
+ }
+
+ #endregion
+ }
+}
diff --git a/src/CSConsole/CSharpLexer.cs b/src/CSConsole/CSharpLexer.cs
new file mode 100644
index 0000000..f4c150c
--- /dev/null
+++ b/src/CSConsole/CSharpLexer.cs
@@ -0,0 +1,308 @@
+using System.Collections.Generic;
+using System.Text;
+using UnityEngine;
+using UnityExplorer.CSConsole.Lexer;
+
+namespace UnityExplorer.CSConsole
+{
+ public struct LexerMatchInfo
+ {
+ public int startIndex;
+ public int endIndex;
+ public string htmlColor;
+ }
+
+ public enum DelimiterType
+ {
+ Start,
+ End,
+ };
+
+ public class CSharpLexer
+ {
+ private string inputString;
+ private readonly Matcher[] matchers;
+ private readonly HashSet startDelimiters;
+ private readonly HashSet endDelimiters;
+ private int currentIndex;
+ private int currentLookaheadIndex;
+
+ public char Current { get; private set; }
+ public char Previous { get; private set; }
+
+ public bool EndOfStream => currentLookaheadIndex >= inputString.Length;
+
+ public static char indentOpen = '{';
+ public static char indentClose = '}';
+ private static StringBuilder indentBuilder = new StringBuilder();
+
+ public static char[] delimiters = new[]
+ {
+ '[', ']', '(', ')', '{', '}', ';', ':', ',', '.'
+ };
+ public static CommentMatch commentMatcher = new CommentMatch();
+ public static SymbolMatch symbolMatcher = new SymbolMatch();
+ public static NumberMatch numberMatcher = new NumberMatch();
+ public static StringMatch stringMatcher = new StringMatch();
+
+ public static KeywordMatch validKeywordMatcher = new KeywordMatch
+ {
+ highlightColor = new Color(0.33f, 0.61f, 0.83f, 1.0f),
+ Keywords = new[] { "add", "as", "ascending", "await", "bool", "break", "by", "byte",
+"case", "catch", "char", "checked", "const", "continue", "decimal", "default", "descending", "do", "dynamic",
+"else", "equals", "false", "finally", "float", "for", "foreach", "from", "global", "goto", "group",
+"if", "in", "int", "into", "is", "join", "let", "lock", "long", "new", "null", "object", "on", "orderby", "out",
+"ref", "remove", "return", "sbyte", "select", "short", "sizeof", "stackalloc", "string",
+"switch", "throw", "true", "try", "typeof", "uint", "ulong", "ushort", "var", "where", "while", "yield" }
+ };
+
+ public static KeywordMatch invalidKeywordMatcher = new KeywordMatch()
+ {
+ highlightColor = new Color(0.95f, 0.10f, 0.10f, 1.0f),
+ Keywords = new[] { "abstract", "async", "base", "class", "delegate", "enum", "explicit", "extern", "fixed", "get",
+"implicit", "interface", "internal", "namespace", "operator", "override", "params", "private", "protected", "public",
+"using", "partial", "readonly", "sealed", "set", "static", "struct", "this", "unchecked", "unsafe", "value", "virtual", "volatile", "void" }
+ };
+
+ // ~~~~~~~ ctor ~~~~~~~
+
+ public CSharpLexer()
+ {
+ startDelimiters = new HashSet(delimiters);
+ endDelimiters = new HashSet(delimiters);
+
+ this.matchers = new Matcher[]
+ {
+ commentMatcher,
+ symbolMatcher,
+ numberMatcher,
+ stringMatcher,
+ validKeywordMatcher,
+ invalidKeywordMatcher,
+ };
+
+ foreach (Matcher lexer in matchers)
+ {
+ foreach (char c in lexer.StartChars)
+ {
+ if (!startDelimiters.Contains(c))
+ startDelimiters.Add(c);
+ }
+
+ foreach (char c in lexer.EndChars)
+ {
+ if (!endDelimiters.Contains(c))
+ endDelimiters.Add(c);
+ }
+ }
+ }
+
+ // ~~~~~~~ Lex Matching ~~~~~~~
+
+ public IEnumerable GetMatches(string input)
+ {
+ if (input == null || matchers == null || matchers.Length == 0)
+ {
+ yield break;
+ }
+
+ inputString = input;
+ Current = ' ';
+ Previous = ' ';
+ currentIndex = 0;
+ currentLookaheadIndex = 0;
+
+ while (!EndOfStream)
+ {
+ bool didMatchLexer = false;
+
+ ReadWhiteSpace();
+
+ foreach (Matcher matcher in matchers)
+ {
+ int startIndex = currentIndex;
+
+ bool isMatched = matcher.IsMatch(this);
+
+ if (isMatched)
+ {
+ int endIndex = currentIndex;
+
+ didMatchLexer = true;
+
+ yield return new LexerMatchInfo
+ {
+ startIndex = startIndex,
+ endIndex = endIndex,
+ htmlColor = matcher.HexColor,
+ };
+
+ break;
+ }
+ }
+
+ if (!didMatchLexer)
+ {
+ ReadNext();
+ Commit();
+ }
+ }
+ }
+
+ // ~~~~~~~ Indent ~~~~~~~
+
+ public static string GetIndentForInput(string input, int indent, out int caretPosition)
+ {
+ indentBuilder = new StringBuilder();
+
+ indent += 1;
+
+ bool stringState = false;
+
+ for (int i = 0; i < input.Length; i++)
+ {
+ if (input[i] == '"')
+ {
+ stringState = !stringState;
+ }
+
+ if (input[i] == '\n')
+ {
+ indentBuilder.Append('\n');
+ for (int j = 0; j < indent; j++)
+ {
+ indentBuilder.Append("\t");
+ }
+ }
+ else if (input[i] == '\t')
+ {
+ continue;
+ }
+ else if (!stringState && input[i] == indentOpen)
+ {
+ indentBuilder.Append(indentOpen);
+ indent++;
+ }
+ else if (!stringState && input[i] == indentClose)
+ {
+ indentBuilder.Append(indentClose);
+ indent--;
+ }
+ else
+ {
+ indentBuilder.Append(input[i]);
+ }
+ }
+
+ string formattedSection = indentBuilder.ToString();
+
+ caretPosition = formattedSection.Length - 1;
+
+ for (int i = formattedSection.Length - 1; i >= 0; i--)
+ {
+ if (formattedSection[i] == '\n')
+ {
+ continue;
+ }
+
+ caretPosition = i;
+ break;
+ }
+
+ return formattedSection;
+ }
+
+ public static int GetIndentLevel(string inputString, int startIndex, int endIndex)
+ {
+ int indent = 0;
+
+ for (int i = startIndex; i < endIndex; i++)
+ {
+ if (inputString[i] == '\t')
+ {
+ indent++;
+ }
+
+ // Check for end line or other characters
+ if (inputString[i] == '\n' || inputString[i] != ' ')
+ {
+ break;
+ }
+ }
+
+ return indent;
+ }
+
+ // Lexer reading
+
+ public char ReadNext()
+ {
+ if (EndOfStream)
+ {
+ return '\0';
+ }
+
+ Previous = Current;
+
+ Current = inputString[currentLookaheadIndex];
+ currentLookaheadIndex++;
+
+ return Current;
+ }
+
+ public void Rollback(int amount = -1)
+ {
+ if (amount == -1)
+ {
+ currentLookaheadIndex = currentIndex;
+ }
+ else
+ {
+ if (currentLookaheadIndex > currentIndex)
+ {
+ currentLookaheadIndex -= amount;
+ }
+ }
+
+ int previousIndex = currentLookaheadIndex - 1;
+
+ if (previousIndex >= inputString.Length)
+ {
+ Previous = inputString[inputString.Length - 1];
+ }
+ else if (previousIndex >= 0)
+ {
+ Previous = inputString[previousIndex];
+ }
+ else
+ {
+ Previous = ' ';
+ }
+ }
+
+ public void Commit()
+ {
+ currentIndex = currentLookaheadIndex;
+ }
+
+ public bool IsSpecialSymbol(char character, DelimiterType position = DelimiterType.Start)
+ {
+ if (position == DelimiterType.Start)
+ {
+ return startDelimiters.Contains(character);
+ }
+
+ return endDelimiters.Contains(character);
+ }
+
+ private void ReadWhiteSpace()
+ {
+ while (char.IsWhiteSpace(ReadNext()) == true)
+ {
+ Commit();
+ }
+
+ Rollback();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CSConsole/CodeEditor.cs b/src/CSConsole/CodeEditor.cs
new file mode 100644
index 0000000..5f3c654
--- /dev/null
+++ b/src/CSConsole/CodeEditor.cs
@@ -0,0 +1,481 @@
+using System;
+using System.Linq;
+using System.Text;
+using UnityExplorer.Input;
+using UnityExplorer.CSConsole.Lexer;
+using UnityEngine;
+using UnityEngine.EventSystems;
+using UnityEngine.UI;
+using UnityExplorer.UI;
+using UnityExplorer.UI.Modules;
+using System.Collections.Generic;
+using System.Reflection;
+using UnityExplorer.UI.Shared;
+using UnityExplorer.Helpers;
+
+namespace UnityExplorer.CSConsole
+{
+ // Handles most of the UI side of the C# console, including syntax highlighting.
+
+ public class CodeEditor
+ {
+ public InputField InputField { get; internal set; }
+ public Text InputText { get; internal set; }
+ public int CurrentIndent { get; private set; }
+
+ public static bool EnableCtrlRShortcut { get; set; } = true;
+ public static bool EnableAutoIndent { get; set; } = true;
+ public static bool EnableAutocompletes { get; set; } = true;
+ public static List AutoCompletes = new List();
+
+ public string HighlightedText => inputHighlightText.text;
+ private Text inputHighlightText;
+
+ private readonly CSharpLexer highlightLexer;
+ private readonly StringBuilder sbHighlight;
+
+ internal int m_lastCaretPos;
+ internal int m_fixCaretPos;
+ internal bool m_fixwanted;
+ internal float m_lastSelectAlpha;
+
+ private static readonly KeyCode[] onFocusKeys =
+ {
+ KeyCode.Return, KeyCode.Backspace, KeyCode.UpArrow,
+ KeyCode.DownArrow, KeyCode.LeftArrow, KeyCode.RightArrow
+ };
+
+ internal const string STARTUP_TEXT = @"Welcome to the UnityExplorer C# Console.
+
+The following helper methods are available:
+
+* Log(""message"") logs a message to the debug console
+
+* CurrentTarget() returns the currently inspected target on the Home page
+
+* AllTargets() returns an object[] array containing all inspected instances
+
+* Inspect(someObject) to inspect an instance, eg. Inspect(Camera.main);
+
+* Inspect(typeof(SomeClass)) to inspect a Class with static reflection
+
+* AddUsing(""SomeNamespace"") adds a using directive to the C# console
+
+* GetUsing() logs the current using directives to the debug console
+
+* Reset() resets all using directives and variables
+";
+
+ public CodeEditor()
+ {
+ sbHighlight = new StringBuilder();
+ highlightLexer = new CSharpLexer();
+
+ ConstructUI();
+
+ InputField.onValueChanged.AddListener((string s) => { OnInputChanged(s); });
+ }
+
+ public void Update()
+ {
+ if (EnableCtrlRShortcut)
+ {
+ if ((InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
+ && InputManager.GetKeyDown(KeyCode.R))
+ {
+ var text = InputField.text.Trim();
+ if (!string.IsNullOrEmpty(text))
+ {
+ CSConsolePage.Instance.Evaluate(text);
+ return;
+ }
+ }
+ }
+
+ if (EnableAutoIndent && InputManager.GetKeyDown(KeyCode.Return))
+ AutoIndentCaret();
+
+ if (EnableAutocompletes && InputField.isFocused)
+ {
+ if (InputManager.GetMouseButton(0) || onFocusKeys.Any(it => InputManager.GetKeyDown(it)))
+ UpdateAutocompletes();
+ }
+
+ if (m_fixCaretPos > 0)
+ {
+ if (!m_fixwanted)
+ {
+ EventSystem.current.SetSelectedGameObject(InputField.gameObject, null);
+ m_fixwanted = true;
+ }
+ else
+ {
+ InputField.caretPosition = m_fixCaretPos;
+ InputField.selectionFocusPosition = m_fixCaretPos;
+
+ m_fixwanted = false;
+ m_fixCaretPos = -1;
+
+ var color = InputField.selectionColor;
+ color.a = m_lastSelectAlpha;
+ InputField.selectionColor = color;
+ }
+ }
+ else if (InputField.caretPosition > 0)
+ {
+ m_lastCaretPos = InputField.caretPosition;
+ }
+ }
+
+ internal void UpdateAutocompletes()
+ {
+ AutoCompleter.CheckAutocomplete();
+ AutoCompleter.SetSuggestions(AutoCompletes.ToArray());
+ }
+
+ public void UseAutocomplete(string suggestion)
+ {
+ string input = InputField.text;
+ input = input.Insert(m_lastCaretPos, suggestion);
+ InputField.text = input;
+
+ m_fixCaretPos = m_lastCaretPos += suggestion.Length;
+
+ var color = InputField.selectionColor;
+ m_lastSelectAlpha = color.a;
+ color.a = 0f;
+ InputField.selectionColor = color;
+
+ AutoCompleter.ClearAutocompletes();
+ }
+
+ public void OnInputChanged(string newInput, bool forceUpdate = false)
+ {
+ string newText = newInput;
+
+ UpdateIndent(newInput);
+
+ if (!forceUpdate && string.IsNullOrEmpty(newText))
+ inputHighlightText.text = string.Empty;
+ else
+ inputHighlightText.text = SyntaxHighlightContent(newText);
+
+ UpdateAutocompletes();
+ }
+
+ private void UpdateIndent(string newText)
+ {
+ int caret = InputField.caretPosition;
+
+ int len = newText.Length;
+ if (caret < 0 || caret >= len)
+ {
+ while (caret >= 0 && caret >= len)
+ caret--;
+
+ if (caret < 0)
+ return;
+ }
+
+ CurrentIndent = 0;
+
+ bool stringState = false;
+
+ for (int i = 0; i < caret && i < newText.Length; i++)
+ {
+ char character = newText[i];
+
+ if (character == '"')
+ stringState = !stringState;
+ else if (!stringState && character == CSharpLexer.indentOpen)
+ CurrentIndent++;
+ else if (!stringState && character == CSharpLexer.indentClose)
+ CurrentIndent--;
+ }
+
+ if (CurrentIndent < 0)
+ CurrentIndent = 0;
+ }
+
+ private const string CLOSE_COLOR_TAG = "";
+
+ private string SyntaxHighlightContent(string inputText)
+ {
+ int offset = 0;
+
+ sbHighlight.Length = 0;
+
+ foreach (LexerMatchInfo match in highlightLexer.GetMatches(inputText))
+ {
+ for (int i = offset; i < match.startIndex; i++)
+ {
+ sbHighlight.Append(inputText[i]);
+ }
+
+ sbHighlight.Append($"{match.htmlColor}");
+
+ for (int i = match.startIndex; i < match.endIndex; i++)
+ {
+ sbHighlight.Append(inputText[i]);
+ }
+
+ sbHighlight.Append(CLOSE_COLOR_TAG);
+
+ offset = match.endIndex;
+ }
+
+ for (int i = offset; i < inputText.Length; i++)
+ {
+ sbHighlight.Append(inputText[i]);
+ }
+
+ inputText = sbHighlight.ToString();
+
+ return inputText;
+ }
+
+ private void AutoIndentCaret()
+ {
+ if (CurrentIndent > 0)
+ {
+ string indent = GetAutoIndentTab(CurrentIndent);
+
+ if (indent.Length > 0)
+ {
+ int caretPos = InputField.caretPosition;
+
+ string indentMinusOne = indent.Substring(0, indent.Length - 1);
+
+ // get last index of {
+ // chuck it on the next line if its not already
+ string text = InputField.text;
+ string sub = InputField.text.Substring(0, InputField.caretPosition);
+ int lastIndex = sub.LastIndexOf("{");
+ int offset = lastIndex - 1;
+ if (offset >= 0 && text[offset] != '\n' && text[offset] != '\t')
+ {
+ string open = "\n" + indentMinusOne;
+
+ InputField.text = text.Insert(offset + 1, open);
+
+ caretPos += open.Length;
+ }
+
+ // check if should add auto-close }
+ int numOpen = InputField.text.Where(x => x == CSharpLexer.indentOpen).Count();
+ int numClose = InputField.text.Where(x => x == CSharpLexer.indentClose).Count();
+
+ if (numOpen > numClose)
+ {
+ // add auto-indent closing
+ indentMinusOne = $"\n{indentMinusOne}}}";
+ InputField.text = InputField.text.Insert(caretPos, indentMinusOne);
+ }
+
+ // insert the actual auto indent now
+ InputField.text = InputField.text.Insert(caretPos, indent);
+
+ //InputField.stringPosition = caretPos + indent.Length;
+ InputField.caretPosition = caretPos + indent.Length;
+ }
+ }
+
+ // Update line column and indent positions
+ UpdateIndent(InputField.text);
+
+ InputText.text = InputField.text;
+ //inputText.SetText(InputField.text, true);
+ InputText.Rebuild(CanvasUpdate.Prelayout);
+ InputField.ForceLabelUpdate();
+ InputField.Rebuild(CanvasUpdate.Prelayout);
+
+ OnInputChanged(InputText.text, true);
+ }
+
+ private string GetAutoIndentTab(int amount)
+ {
+ string tab = string.Empty;
+
+ for (int i = 0; i < amount; i++)
+ {
+ tab += "\t";
+ }
+
+ return tab;
+ }
+
+ // ========== UI CONSTRUCTION =========== //
+
+ public void ConstructUI()
+ {
+ CSConsolePage.Instance.Content = UIFactory.CreateUIObject("C# Console", MainMenu.Instance.PageViewport);
+
+ var mainLayout = CSConsolePage.Instance.Content.AddComponent();
+ mainLayout.preferredHeight = 500;
+ mainLayout.flexibleHeight = 9000;
+
+ var mainGroup = CSConsolePage.Instance.Content.AddComponent();
+ mainGroup.childControlHeight = true;
+ mainGroup.childControlWidth = true;
+ mainGroup.childForceExpandHeight = true;
+ mainGroup.childForceExpandWidth = true;
+
+ #region TOP BAR
+
+ // Main group object
+
+ var topBarObj = UIFactory.CreateHorizontalGroup(CSConsolePage.Instance.Content);
+ LayoutElement topBarLayout = topBarObj.AddComponent();
+ topBarLayout.minHeight = 50;
+ topBarLayout.flexibleHeight = 0;
+
+ var topBarGroup = topBarObj.GetComponent();
+ topBarGroup.padding.left = 30;
+ topBarGroup.padding.right = 30;
+ topBarGroup.padding.top = 8;
+ topBarGroup.padding.bottom = 8;
+ topBarGroup.spacing = 10;
+ topBarGroup.childForceExpandHeight = true;
+ topBarGroup.childForceExpandWidth = true;
+ topBarGroup.childControlWidth = true;
+ topBarGroup.childControlHeight = true;
+ topBarGroup.childAlignment = TextAnchor.LowerCenter;
+
+ var topBarLabel = UIFactory.CreateLabel(topBarObj, TextAnchor.MiddleLeft);
+ var topBarLabelLayout = topBarLabel.AddComponent();
+ topBarLabelLayout.preferredWidth = 150;
+ topBarLabelLayout.flexibleWidth = 5000;
+ var topBarText = topBarLabel.GetComponent();
+ topBarText.text = "C# Console";
+ topBarText.fontSize = 20;
+
+ // Enable Ctrl+R toggle
+
+ var ctrlRToggleObj = UIFactory.CreateToggle(topBarObj, out Toggle ctrlRToggle, out Text ctrlRToggleText);
+ ctrlRToggle.onValueChanged.AddListener(CtrlRToggleCallback);
+ void CtrlRToggleCallback(bool val)
+ {
+ EnableCtrlRShortcut = val;
+ }
+
+ ctrlRToggleText.text = "Run on Ctrl+R";
+ ctrlRToggleText.alignment = TextAnchor.UpperLeft;
+ var ctrlRLayout = ctrlRToggleObj.AddComponent();
+ ctrlRLayout.minWidth = 140;
+ ctrlRLayout.flexibleWidth = 0;
+ ctrlRLayout.minHeight = 25;
+
+ // Enable Suggestions toggle
+
+ var suggestToggleObj = UIFactory.CreateToggle(topBarObj, out Toggle suggestToggle, out Text suggestToggleText);
+ suggestToggle.onValueChanged.AddListener(SuggestToggleCallback);
+ void SuggestToggleCallback(bool val)
+ {
+ EnableAutocompletes = val;
+ AutoCompleter.Update();
+ }
+
+ suggestToggleText.text = "Suggestions";
+ suggestToggleText.alignment = TextAnchor.UpperLeft;
+ var suggestLayout = suggestToggleObj.AddComponent();
+ suggestLayout.minWidth = 120;
+ suggestLayout.flexibleWidth = 0;
+ suggestLayout.minHeight = 25;
+
+ // Enable Auto-indent toggle
+
+ var autoIndentToggleObj = UIFactory.CreateToggle(topBarObj, out Toggle autoIndentToggle, out Text autoIndentToggleText);
+ autoIndentToggle.onValueChanged.AddListener(OnIndentChanged);
+ void OnIndentChanged(bool val) => EnableAutoIndent = val;
+
+ autoIndentToggleText.text = "Auto-indent on Enter";
+ autoIndentToggleText.alignment = TextAnchor.UpperLeft;
+
+ var autoIndentLayout = autoIndentToggleObj.AddComponent();
+ autoIndentLayout.minWidth = 180;
+ autoIndentLayout.flexibleWidth = 0;
+ autoIndentLayout.minHeight = 25;
+
+ #endregion
+
+ #region CONSOLE INPUT
+
+ int fontSize = 16;
+
+ var inputObj = UIFactory.CreateSrollInputField(CSConsolePage.Instance.Content, out InputFieldScroller consoleScroll, fontSize);
+
+ var inputField = consoleScroll.inputField;
+
+ var mainTextObj = inputField.textComponent.gameObject;
+ var mainTextInput = inputField.textComponent;
+ mainTextInput.supportRichText = false;
+ mainTextInput.color = new Color(1, 1, 1, 0.5f);
+
+ var placeHolderText = inputField.placeholder.GetComponent();
+ placeHolderText.text = STARTUP_TEXT;
+ placeHolderText.fontSize = fontSize;
+
+ var highlightTextObj = UIFactory.CreateUIObject("HighlightText", mainTextObj.gameObject);
+ var highlightTextRect = highlightTextObj.GetComponent();
+ highlightTextRect.pivot = new Vector2(0, 1);
+ highlightTextRect.anchorMin = Vector2.zero;
+ highlightTextRect.anchorMax = Vector2.one;
+ highlightTextRect.offsetMin = new Vector2(20, 0);
+ highlightTextRect.offsetMax = new Vector2(14, 0);
+
+ var highlightTextInput = highlightTextObj.AddComponent();
+ highlightTextInput.supportRichText = true;
+ highlightTextInput.fontSize = fontSize;
+
+ #endregion
+
+ #region COMPILE BUTTON
+
+ var compileBtnObj = UIFactory.CreateButton(CSConsolePage.Instance.Content);
+ var compileBtnLayout = compileBtnObj.AddComponent();
+ compileBtnLayout.preferredWidth = 80;
+ compileBtnLayout.flexibleWidth = 0;
+ compileBtnLayout.minHeight = 45;
+ compileBtnLayout.flexibleHeight = 0;
+ var compileButton = compileBtnObj.GetComponent();
+ var compileBtnColors = compileButton.colors;
+ compileBtnColors.normalColor = new Color(14f / 255f, 80f / 255f, 14f / 255f);
+ compileButton.colors = compileBtnColors;
+ var btnText = compileBtnObj.GetComponentInChildren();
+ btnText.text = "Run";
+ btnText.fontSize = 18;
+ btnText.color = Color.white;
+
+ // Set compile button callback now that we have the Input Field reference
+ compileButton.onClick.AddListener(CompileCallback);
+ void CompileCallback()
+ {
+ if (!string.IsNullOrEmpty(inputField.text))
+ {
+ CSConsolePage.Instance.Evaluate(inputField.text.Trim());
+ }
+ }
+
+ #endregion
+
+ //mainTextInput.supportRichText = false;
+
+ mainTextInput.font = UIManager.ConsoleFont;
+ placeHolderText.font = UIManager.ConsoleFont;
+ highlightTextInput.font = UIManager.ConsoleFont;
+
+ // reset this after formatting finalized
+ highlightTextRect.anchorMin = Vector2.zero;
+ highlightTextRect.anchorMax = Vector2.one;
+ highlightTextRect.offsetMin = Vector2.zero;
+ highlightTextRect.offsetMax = Vector2.zero;
+
+ // assign references
+
+ this.InputField = inputField;
+
+ this.InputText = mainTextInput;
+ this.inputHighlightText = highlightTextInput;
+ }
+ }
+}
diff --git a/src/CSConsole/Lexer/CommentMatch.cs b/src/CSConsole/Lexer/CommentMatch.cs
new file mode 100644
index 0000000..1a45558
--- /dev/null
+++ b/src/CSConsole/Lexer/CommentMatch.cs
@@ -0,0 +1,46 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace UnityExplorer.CSConsole.Lexer
+{
+ public class CommentMatch : Matcher
+ {
+ public string lineCommentStart = @"//";
+ public string blockCommentStart = @"/*";
+ public string blockCommentEnd = @"*/";
+
+ public override Color HighlightColor => new Color(0.34f, 0.65f, 0.29f, 1.0f);
+ public override IEnumerable StartChars => new char[] { lineCommentStart[0], blockCommentStart[0] };
+ public override IEnumerable EndChars => new char[] { blockCommentEnd[0] };
+ public override bool IsImplicitMatch(CSharpLexer lexer) => IsMatch(lexer, lineCommentStart) || IsMatch(lexer, blockCommentStart);
+
+ private bool IsMatch(CSharpLexer lexer, string commentType)
+ {
+ if (!string.IsNullOrEmpty(commentType))
+ {
+ lexer.Rollback();
+
+ bool match = true;
+ for (int i = 0; i < commentType.Length; i++)
+ {
+ if (commentType[i] != lexer.ReadNext())
+ {
+ match = false;
+ break;
+ }
+ }
+
+ if (match)
+ {
+ // Read until end of line or file
+ while (!IsEndLineOrEndFile(lexer, lexer.ReadNext())) { }
+
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private bool IsEndLineOrEndFile(CSharpLexer lexer, char character) => lexer.EndOfStream || character == '\n' || character == '\r';
+ }
+}
diff --git a/src/CSConsole/Lexer/KeywordMatch.cs b/src/CSConsole/Lexer/KeywordMatch.cs
new file mode 100644
index 0000000..5926652
--- /dev/null
+++ b/src/CSConsole/Lexer/KeywordMatch.cs
@@ -0,0 +1,97 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace UnityExplorer.CSConsole.Lexer
+{
+ // I use two different KeywordMatch instances (valid and invalid).
+ // This class just contains common implementations.
+ public class KeywordMatch : Matcher
+ {
+ public string[] Keywords;
+
+ public override Color HighlightColor => highlightColor;
+ public Color highlightColor;
+
+ private readonly HashSet shortlist = new HashSet();
+ private readonly Stack removeList = new Stack();
+
+ public override bool IsImplicitMatch(CSharpLexer lexer)
+ {
+ if (!char.IsWhiteSpace(lexer.Previous) &&
+ !lexer.IsSpecialSymbol(lexer.Previous, DelimiterType.End))
+ {
+ return false;
+ }
+
+ shortlist.Clear();
+
+ int currentIndex = 0;
+ char currentChar = lexer.ReadNext();
+
+ for (int i = 0; i < Keywords.Length; i++)
+ {
+ if (Keywords[i][0] == currentChar)
+ {
+ shortlist.Add(Keywords[i]);
+ }
+ }
+
+ if (shortlist.Count == 0)
+ {
+ return false;
+ }
+
+ do
+ {
+ if (lexer.EndOfStream)
+ {
+ RemoveLongStrings(currentIndex + 1);
+ break;
+ }
+
+ currentChar = lexer.ReadNext();
+ currentIndex++;
+
+ if (char.IsWhiteSpace(currentChar) ||
+ lexer.IsSpecialSymbol(currentChar, DelimiterType.Start))
+ {
+ RemoveLongStrings(currentIndex);
+ lexer.Rollback(1);
+ break;
+ }
+
+ foreach (string keyword in shortlist)
+ {
+ if (currentIndex >= keyword.Length || keyword[currentIndex] != currentChar)
+ {
+ removeList.Push(keyword);
+ }
+ }
+
+ while (removeList.Count > 0)
+ {
+ shortlist.Remove(removeList.Pop());
+ }
+ }
+ while (shortlist.Count > 0);
+
+ return shortlist.Count > 0;
+ }
+
+ private void RemoveLongStrings(int length)
+ {
+ foreach (string keyword in shortlist)
+ {
+ if (keyword.Length > length)
+ {
+ removeList.Push(keyword);
+ }
+ }
+
+ while (removeList.Count > 0)
+ {
+ shortlist.Remove(removeList.Pop());
+ }
+ }
+ }
+}
diff --git a/src/CSConsole/Lexer/Matcher.cs b/src/CSConsole/Lexer/Matcher.cs
new file mode 100644
index 0000000..fff017b
--- /dev/null
+++ b/src/CSConsole/Lexer/Matcher.cs
@@ -0,0 +1,32 @@
+using System.Collections.Generic;
+using UnityExplorer.Unstrip;
+using UnityEngine;
+using System.Linq;
+
+namespace UnityExplorer.CSConsole.Lexer
+{
+ public abstract class Matcher
+ {
+ public abstract Color HighlightColor { get; }
+
+ public string HexColor => htmlColor ?? (htmlColor = "");
+ private string htmlColor;
+
+ public virtual IEnumerable StartChars => Enumerable.Empty();
+ public virtual IEnumerable EndChars => Enumerable.Empty();
+
+ public abstract bool IsImplicitMatch(CSharpLexer lexer);
+
+ public bool IsMatch(CSharpLexer lexer)
+ {
+ if (IsImplicitMatch(lexer))
+ {
+ lexer.Commit();
+ return true;
+ }
+
+ lexer.Rollback();
+ return false;
+ }
+ }
+}
diff --git a/src/CSConsole/Lexer/NumberMatch.cs b/src/CSConsole/Lexer/NumberMatch.cs
new file mode 100644
index 0000000..f4c6e22
--- /dev/null
+++ b/src/CSConsole/Lexer/NumberMatch.cs
@@ -0,0 +1,39 @@
+using UnityEngine;
+
+namespace UnityExplorer.CSConsole.Lexer
+{
+ public class NumberMatch : Matcher
+ {
+ public override Color HighlightColor => new Color(0.58f, 0.33f, 0.33f, 1.0f);
+
+ public override bool IsImplicitMatch(CSharpLexer lexer)
+ {
+ if (!char.IsWhiteSpace(lexer.Previous) &&
+ !lexer.IsSpecialSymbol(lexer.Previous, DelimiterType.End))
+ {
+ return false;
+ }
+
+ bool matchedNumber = false;
+
+ while (!lexer.EndOfStream)
+ {
+ if (IsNumberOrDecimalPoint(lexer.ReadNext()))
+ {
+ matchedNumber = true;
+ lexer.Commit();
+ }
+ else
+ {
+ lexer.Rollback();
+ break;
+ }
+ }
+
+ return matchedNumber;
+ }
+
+ private bool IsNumberOrDecimalPoint(char character) => char.IsNumber(character) || character == '.';
+ }
+
+}
diff --git a/src/CSConsole/Lexer/StringMatch.cs b/src/CSConsole/Lexer/StringMatch.cs
new file mode 100644
index 0000000..8b9bace
--- /dev/null
+++ b/src/CSConsole/Lexer/StringMatch.cs
@@ -0,0 +1,26 @@
+using System.Collections.Generic;
+using UnityEngine;
+
+namespace UnityExplorer.CSConsole.Lexer
+{
+ public class StringMatch : Matcher
+ {
+ public override Color HighlightColor => new Color(0.79f, 0.52f, 0.32f, 1.0f);
+
+ public override IEnumerable StartChars => new[] { '"' };
+ public override IEnumerable EndChars => new[] { '"' };
+
+ public override bool IsImplicitMatch(CSharpLexer lexer)
+ {
+ if (lexer.ReadNext() == '"')
+ {
+ while (!IsClosingQuoteOrEndFile(lexer, lexer.ReadNext())) { }
+
+ return true;
+ }
+ return false;
+ }
+
+ private bool IsClosingQuoteOrEndFile(CSharpLexer lexer, char character) => lexer.EndOfStream || character == '"';
+ }
+}
diff --git a/src/CSConsole/Lexer/SymbolMatch.cs b/src/CSConsole/Lexer/SymbolMatch.cs
new file mode 100644
index 0000000..7a1b9e2
--- /dev/null
+++ b/src/CSConsole/Lexer/SymbolMatch.cs
@@ -0,0 +1,106 @@
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+
+namespace UnityExplorer.CSConsole.Lexer
+{
+ public class SymbolMatch : Matcher
+ {
+ public override Color HighlightColor => new Color(0.58f, 0.47f, 0.37f, 1.0f);
+
+ private readonly string[] symbols = new[]
+ {
+ "[", "]", "(", ")", ".", "?", ":", "+", "-", "*", "/", "%", "&", "|", "^", "~", "=", "<", ">",
+ "++", "--", "&&", "||", "<<", ">>", "==", "!=", "<=", ">=", "+=", "-=", "*=", "/=", "%=", "&=",
+ "|=", "^=", "<<=", ">>=", "->", "??", "=>",
+ };
+
+ private static readonly List shortlist = new List();
+ private static readonly Stack removeList = new Stack();
+
+ public override IEnumerable StartChars => symbols.Select(s => s[0]);
+ public override IEnumerable EndChars => symbols.Select(s => s[0]);
+
+ public override bool IsImplicitMatch(CSharpLexer lexer)
+ {
+ if (lexer == null)
+ return false;
+
+ if (!char.IsWhiteSpace(lexer.Previous) &&
+ !char.IsLetter(lexer.Previous) &&
+ !char.IsDigit(lexer.Previous) &&
+ !lexer.IsSpecialSymbol(lexer.Previous, DelimiterType.End))
+ {
+ return false;
+ }
+
+ shortlist.Clear();
+
+ int currentIndex = 0;
+ char currentChar = lexer.ReadNext();
+
+ for (int i = symbols.Length - 1; i >= 0; i--)
+ {
+ if (symbols[i][0] == currentChar)
+ shortlist.Add(symbols[i]);
+ }
+
+ if (shortlist.Count == 0)
+ return false;
+
+ do
+ {
+ if (lexer.EndOfStream)
+ {
+ RemoveLongStrings(currentIndex + 1);
+ break;
+ }
+
+ currentChar = lexer.ReadNext();
+ currentIndex++;
+
+ if (char.IsWhiteSpace(currentChar) ||
+ char.IsLetter(currentChar) ||
+ char.IsDigit(currentChar) ||
+ lexer.IsSpecialSymbol(currentChar, DelimiterType.Start))
+ {
+ RemoveLongStrings(currentIndex);
+ lexer.Rollback(1);
+ break;
+ }
+
+ foreach (string symbol in shortlist)
+ {
+ if (currentIndex >= symbol.Length || symbol[currentIndex] != currentChar)
+ {
+ removeList.Push(symbol);
+ }
+ }
+
+ while (removeList.Count > 0)
+ {
+ shortlist.Remove(removeList.Pop());
+ }
+ }
+ while (shortlist.Count > 0);
+
+ return shortlist.Count > 0;
+ }
+
+ private void RemoveLongStrings(int length)
+ {
+ foreach (string keyword in shortlist)
+ {
+ if (keyword.Length > length)
+ {
+ removeList.Push(keyword);
+ }
+ }
+
+ while (removeList.Count > 0)
+ {
+ shortlist.Remove(removeList.Pop());
+ }
+ }
+ }
+}
diff --git a/src/UI/Main/Console/ScriptEvaluator.cs b/src/CSConsole/ScriptEvaluator.cs
similarity index 75%
rename from src/UI/Main/Console/ScriptEvaluator.cs
rename to src/CSConsole/ScriptEvaluator.cs
index eb827a6..8a8824a 100644
--- a/src/UI/Main/Console/ScriptEvaluator.cs
+++ b/src/CSConsole/ScriptEvaluator.cs
@@ -6,20 +6,20 @@ using Mono.CSharp;
// Thanks to ManlyMarco for this
-namespace Explorer.UI.Main
+namespace UnityExplorer.CSConsole
{
- internal class ScriptEvaluator : Evaluator, IDisposable
+ public class ScriptEvaluator : Evaluator, IDisposable
{
- private static readonly HashSet StdLib = new HashSet(StringComparer.InvariantCultureIgnoreCase)
+ private static readonly HashSet StdLib = new HashSet(StringComparer.InvariantCultureIgnoreCase)
{
- "mscorlib", "System.Core", "System", "System.Xml"
+ "mscorlib", "System.Core", "System", "System.Xml"
};
- private readonly TextWriter _logger;
+ private readonly TextWriter tw;
- public ScriptEvaluator(TextWriter logger) : base(BuildContext(logger))
+ public ScriptEvaluator(TextWriter tw) : base(BuildContext(tw))
{
- _logger = logger;
+ this.tw = tw;
ImportAppdomainAssemblies(ReferenceAssembly);
AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad;
@@ -28,14 +28,17 @@ namespace Explorer.UI.Main
public void Dispose()
{
AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad;
- _logger.Dispose();
+ tw.Dispose();
}
private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args)
{
string name = args.LoadedAssembly.GetName().Name;
if (StdLib.Contains(name))
+ {
return;
+ }
+
ReferenceAssembly(args.LoadedAssembly);
}
@@ -58,11 +61,14 @@ namespace Explorer.UI.Main
private static void ImportAppdomainAssemblies(Action import)
{
- foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
+ foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies())
{
string name = assembly.GetName().Name;
if (StdLib.Contains(name))
+ {
continue;
+ }
+
import(assembly);
}
}
diff --git a/src/CSConsole/ScriptInteraction.cs b/src/CSConsole/ScriptInteraction.cs
new file mode 100644
index 0000000..8efa1ee
--- /dev/null
+++ b/src/CSConsole/ScriptInteraction.cs
@@ -0,0 +1,57 @@
+using System;
+using Mono.CSharp;
+using UnityExplorer.UI;
+using UnityExplorer.UI.Modules;
+using UnityExplorer.Inspectors;
+
+namespace UnityExplorer.CSConsole
+{
+ public class ScriptInteraction : InteractiveBase
+ {
+ public static void Log(object message)
+ {
+ ExplorerCore.Log(message);
+ }
+
+ public static void AddUsing(string directive)
+ {
+ CSConsolePage.Instance.AddUsing(directive);
+ }
+
+ public static void GetUsing()
+ {
+ ExplorerCore.Log(CSConsolePage.Instance.m_evaluator.GetUsing());
+ }
+
+ public static void Reset()
+ {
+ CSConsolePage.Instance.ResetConsole();
+ }
+
+ public static object CurrentTarget()
+ {
+ return InspectorManager.Instance?.m_activeInspector?.Target;
+ }
+
+ public static object[] AllTargets()
+ {
+ int count = InspectorManager.Instance?.m_currentInspectors.Count ?? 0;
+ object[] ret = new object[count];
+ for (int i = 0; i < count; i++)
+ {
+ ret[i] = InspectorManager.Instance?.m_currentInspectors[i].Target;
+ }
+ return ret;
+ }
+
+ public static void Inspect(object obj)
+ {
+ InspectorManager.Instance.Inspect(obj);
+ }
+
+ public static void Inspect(Type type)
+ {
+ InspectorManager.Instance.Inspect(type);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CSConsole/Suggestion.cs b/src/CSConsole/Suggestion.cs
new file mode 100644
index 0000000..caa632f
--- /dev/null
+++ b/src/CSConsole/Suggestion.cs
@@ -0,0 +1,69 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using UnityEngine;
+using UnityExplorer.Helpers;
+
+namespace UnityExplorer.CSConsole
+{
+ public struct Suggestion
+ {
+ public enum Contexts
+ {
+ Namespace,
+ Keyword,
+ Other
+ }
+
+ // ~~~~ Instance ~~~~
+
+ public readonly string Prefix;
+ public readonly string Addition;
+ public readonly Contexts Context;
+
+ public string Full => Prefix + Addition;
+
+ public Color TextColor => GetTextColor();
+
+ public Suggestion(string addition, string prefix, Contexts type)
+ {
+ Addition = addition;
+ Prefix = prefix;
+ Context = type;
+ }
+
+ private Color GetTextColor()
+ {
+ switch (Context)
+ {
+ case Contexts.Namespace: return Color.grey;
+ case Contexts.Keyword: return keywordColor;
+ default: return Color.white;
+ }
+ }
+
+ // ~~~~ Static ~~~~
+
+ public static HashSet Namespaces => m_namspaces ?? GetNamespaces();
+ private static HashSet m_namspaces;
+
+ public static HashSet Keywords => m_keywords ?? (m_keywords = new HashSet(CSharpLexer.validKeywordMatcher.Keywords));
+ private static HashSet m_keywords;
+
+ private static readonly Color keywordColor = new Color(80f / 255f, 150f / 255f, 215f / 255f);
+
+ private static HashSet GetNamespaces()
+ {
+ HashSet set = new HashSet(
+ AppDomain.CurrentDomain.GetAssemblies()
+ .SelectMany(GetTypes)
+ .Where(x => x.IsPublic && !string.IsNullOrEmpty(x.Namespace))
+ .Select(x => x.Namespace));
+
+ return m_namspaces = set;
+
+ IEnumerable GetTypes(Assembly asm) => asm.TryGetTypes();
+ }
+ }
+}
diff --git a/src/CacheObject/CacheEnumerated.cs b/src/CacheObject/CacheEnumerated.cs
deleted file mode 100644
index 3f7d172..0000000
--- a/src/CacheObject/CacheEnumerated.cs
+++ /dev/null
@@ -1,26 +0,0 @@
-using System;
-using System.Collections;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using Explorer.UI;
-
-namespace Explorer.CacheObject
-{
- public class CacheEnumerated : CacheObjectBase
- {
- public int Index { get; set; }
- public IList RefIList { get; set; }
- public InteractiveEnumerable ParentEnumeration { get; set; }
-
- public override bool CanWrite => RefIList != null && ParentEnumeration.OwnerCacheObject.CanWrite;
-
- public override void SetValue()
- {
- RefIList[Index] = IValue.Value;
- ParentEnumeration.Value = RefIList;
-
- ParentEnumeration.OwnerCacheObject.SetValue();
- }
- }
-}
diff --git a/src/CacheObject/CacheFactory.cs b/src/CacheObject/CacheFactory.cs
deleted file mode 100644
index 3a881b5..0000000
--- a/src/CacheObject/CacheFactory.cs
+++ /dev/null
@@ -1,76 +0,0 @@
-using System;
-using System.Reflection;
-using Explorer.CacheObject;
-using UnityEngine;
-using Explorer.Helpers;
-
-namespace Explorer
-{
- public static class CacheFactory
- {
- public static CacheObjectBase GetCacheObject(object obj)
- {
- if (obj == null) return null;
-
- return GetCacheObject(obj, ReflectionHelpers.GetActualType(obj));
- }
-
- public static CacheObjectBase GetCacheObject(object obj, Type type)
- {
- var ret = new CacheObjectBase();
- ret.Init(obj, type);
- return ret;
- }
-
- public static CacheMember GetCacheObject(MemberInfo member, object declaringInstance)
- {
- CacheMember ret;
-
- if (member is MethodInfo mi && CanProcessArgs(mi.GetParameters()))
- {
- ret = new CacheMethod();
- ret.InitMember(mi, declaringInstance);
- }
- else if (member is PropertyInfo pi && CanProcessArgs(pi.GetIndexParameters()))
- {
- ret = new CacheProperty();
- ret.InitMember(pi, declaringInstance);
- }
- else if (member is FieldInfo fi)
- {
- ret = new CacheField();
- ret.InitMember(fi, declaringInstance);
- }
- else
- {
- return null;
- }
-
- return ret;
- }
-
- public static bool CanProcessArgs(ParameterInfo[] parameters)
- {
- foreach (var param in parameters)
- {
- var pType = param.ParameterType;
-
- if (pType.IsByRef && pType.HasElementType)
- {
- pType = pType.GetElementType();
- }
-
- if (pType.IsPrimitive || pType == typeof(string))
- {
- continue;
- }
- else
- {
- return false;
- }
- }
-
- return true;
- }
- }
-}
diff --git a/src/CacheObject/CacheField.cs b/src/CacheObject/CacheField.cs
deleted file mode 100644
index 2aaa748..0000000
--- a/src/CacheObject/CacheField.cs
+++ /dev/null
@@ -1,54 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Reflection;
-using Explorer.UI;
-using Explorer.Helpers;
-
-namespace Explorer.CacheObject
-{
- public class CacheField : CacheMember
- {
- public override bool IsStatic => (MemInfo as FieldInfo).IsStatic;
-
- public override void InitMember(MemberInfo member, object declaringInstance)
- {
- base.InitMember(member, declaringInstance);
-
- base.Init(null, (member as FieldInfo).FieldType);
-
- UpdateValue();
- }
-
- public override void UpdateValue()
- {
- if (IValue is InteractiveDictionary iDict)
- {
- if (!iDict.EnsureDictionaryIsSupported())
- {
- ReflectionException = "Not supported due to TypeInitializationException";
- return;
- }
- }
-
- try
- {
- var fi = MemInfo as FieldInfo;
- IValue.Value = fi.GetValue(fi.IsStatic ? null : DeclaringInstance);
-
- base.UpdateValue();
- }
- catch (Exception e)
- {
- ReflectionException = ReflectionHelpers.ExceptionToString(e);
- }
- }
-
- public override void SetValue()
- {
- var fi = MemInfo as FieldInfo;
- fi.SetValue(fi.IsStatic ? null : DeclaringInstance, IValue.Value);
- }
- }
-}
diff --git a/src/CacheObject/CacheMember.cs b/src/CacheObject/CacheMember.cs
deleted file mode 100644
index e896ac7..0000000
--- a/src/CacheObject/CacheMember.cs
+++ /dev/null
@@ -1,226 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using UnityEngine;
-using Explorer.UI;
-using Explorer.UI.Shared;
-
-namespace Explorer.CacheObject
-{
- public class CacheMember : CacheObjectBase
- {
- public MemberInfo MemInfo { get; set; }
- public Type DeclaringType { get; set; }
- public object DeclaringInstance { get; set; }
-
- public virtual bool IsStatic { get; private set; }
-
- public override bool HasParameters => m_arguments != null && m_arguments.Length > 0;
- public override bool IsMember => true;
-
- public string RichTextName => m_richTextName ?? GetRichTextName();
- private string m_richTextName;
-
- public override bool CanWrite => m_canWrite ?? GetCanWrite();
- private bool? m_canWrite;
-
- public string ReflectionException { get; set; }
-
- public bool m_evaluated = false;
- public bool m_isEvaluating;
- public ParameterInfo[] m_arguments = new ParameterInfo[0];
- public string[] m_argumentInput = new string[0];
-
- public virtual void InitMember(MemberInfo member, object declaringInstance)
- {
- MemInfo = member;
- DeclaringInstance = declaringInstance;
- DeclaringType = member.DeclaringType;
- }
-
- public override void UpdateValue()
- {
- base.UpdateValue();
- }
-
- public override void SetValue()
- {
- // ...
- }
-
- public object[] ParseArguments()
- {
- if (m_arguments.Length < 1)
- {
- return new object[0];
- }
-
- var parsedArgs = new List();
- for (int i = 0; i < m_arguments.Length; i++)
- {
- var input = m_argumentInput[i];
- var type = m_arguments[i].ParameterType;
-
- if (type.IsByRef)
- {
- type = type.GetElementType();
- }
-
- if (!string.IsNullOrEmpty(input))
- {
- if (type == typeof(string))
- {
- parsedArgs.Add(input);
- continue;
- }
- else
- {
- try
- {
- var arg = type.GetMethod("Parse", new Type[] { typeof(string) })
- .Invoke(null, new object[] { input });
-
- parsedArgs.Add(arg);
- continue;
- }
- catch
- {
- ExplorerCore.Log($"Argument #{i} '{m_arguments[i].Name}' ({type.Name}), could not parse input '{input}'.");
- }
- }
- }
-
- // No input, see if there is a default value.
- if (HasDefaultValue(m_arguments[i]))
- {
- parsedArgs.Add(m_arguments[i].DefaultValue);
- continue;
- }
-
- // Try add a null arg I guess
- parsedArgs.Add(null);
- }
-
- return parsedArgs.ToArray();
- }
-
- public static bool HasDefaultValue(ParameterInfo arg) => arg.DefaultValue != DBNull.Value;
-
- public void DrawArgsInput()
- {
- GUILayout.Label($"Arguments: ", new GUILayoutOption[0]);
- for (int i = 0; i < this.m_arguments.Length; i++)
- {
- var name = this.m_arguments[i].Name;
- var input = this.m_argumentInput[i];
- var type = this.m_arguments[i].ParameterType.Name;
-
- var label = $"{type} ";
- label += $"{name} ";
- if (HasDefaultValue(this.m_arguments[i]))
- {
- label = $"[{label} = {this.m_arguments[i].DefaultValue ?? "null"}] ";
- }
-
- GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
-
- GUI.skin.label.alignment = TextAnchor.MiddleCenter;
-
- GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(15) });
- GUILayout.Label(label, new GUILayoutOption[] { GUIHelper.ExpandWidth(false) });
- this.m_argumentInput[i] = GUIHelper.TextField(input, new GUILayoutOption[] { GUIHelper.ExpandWidth(true) });
-
- GUI.skin.label.alignment = TextAnchor.MiddleLeft;
-
- GUILayout.EndHorizontal();
- }
- }
-
- private bool GetCanWrite()
- {
- if (MemInfo is FieldInfo fi)
- m_canWrite = !(fi.IsLiteral && !fi.IsInitOnly);
- else if (MemInfo is PropertyInfo pi)
- m_canWrite = pi.CanWrite;
- else
- m_canWrite = false;
-
- return (bool)m_canWrite;
- }
-
- private string GetRichTextName()
- {
- string memberColor = "";
- bool isStatic = false;
-
- if (MemInfo is FieldInfo fi)
- {
- if (fi.IsStatic)
- {
- isStatic = true;
- memberColor = Syntax.Field_Static;
- }
- else
- memberColor = Syntax.Field_Instance;
- }
- else if (MemInfo is MethodInfo mi)
- {
- if (mi.IsStatic)
- {
- isStatic = true;
- memberColor = Syntax.Method_Static;
- }
- else
- memberColor = Syntax.Method_Instance;
- }
- else if (MemInfo is PropertyInfo pi)
- {
- if (pi.GetAccessors()[0].IsStatic)
- {
- isStatic = true;
- memberColor = Syntax.Prop_Static;
- }
- else
- memberColor = Syntax.Prop_Instance;
- }
-
- string classColor;
- if (MemInfo.DeclaringType.IsValueType)
- {
- classColor = Syntax.StructGreen;
- }
- else if (MemInfo.DeclaringType.IsAbstract && MemInfo.DeclaringType.IsSealed)
- {
- classColor = Syntax.Class_Static;
- }
- else
- {
- classColor = Syntax.Class_Instance;
- }
-
- m_richTextName = $"{MemInfo.DeclaringType.Name} .";
- if (isStatic) m_richTextName += "";
- m_richTextName += $"{MemInfo.Name} ";
- if (isStatic) m_richTextName += " ";
-
- // generic method args
- if (this is CacheMethod cm && cm.GenericArgs.Length > 0)
- {
- m_richTextName += "<";
-
- var args = "";
- for (int i = 0; i < cm.GenericArgs.Length; i++)
- {
- if (args != "") args += ", ";
- args += $"{cm.GenericArgs[i].Name} ";
- }
- m_richTextName += args;
-
- m_richTextName += ">";
- }
-
- return m_richTextName;
- }
- }
-}
diff --git a/src/CacheObject/CacheMethod.cs b/src/CacheObject/CacheMethod.cs
deleted file mode 100644
index 36ecc17..0000000
--- a/src/CacheObject/CacheMethod.cs
+++ /dev/null
@@ -1,200 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using UnityEngine;
-using Explorer.UI.Shared;
-using Explorer.Helpers;
-
-namespace Explorer.CacheObject
-{
- public class CacheMethod : CacheMember
- {
- private CacheObjectBase m_cachedReturnValue;
-
- public override bool HasParameters => base.HasParameters || GenericArgs.Length > 0;
-
- public override bool IsStatic => (MemInfo as MethodInfo).IsStatic;
-
- public Type[] GenericArgs { get; private set; }
- public Type[][] GenericConstraints { get; private set; }
-
- public string[] GenericArgInput = new string[0];
-
- public override void InitMember(MemberInfo member, object declaringInstance)
- {
- base.InitMember(member, declaringInstance);
-
- var mi = MemInfo as MethodInfo;
- GenericArgs = mi.GetGenericArguments();
-
- GenericConstraints = GenericArgs.Select(x => x.GetGenericParameterConstraints())
- .ToArray();
-
- GenericArgInput = new string[GenericArgs.Length];
-
- m_arguments = mi.GetParameters();
- m_argumentInput = new string[m_arguments.Length];
-
- base.Init(null, mi.ReturnType);
- }
-
- public override void UpdateValue()
- {
- // CacheMethod cannot UpdateValue directly. Need to Evaluate.
- }
-
- public void Evaluate()
- {
- MethodInfo mi;
- if (GenericArgs.Length > 0)
- {
- mi = MakeGenericMethodFromInput();
- if (mi == null) return;
- }
- else
- {
- mi = MemInfo as MethodInfo;
- }
-
- object ret = null;
-
- try
- {
- ret = mi.Invoke(mi.IsStatic ? null : DeclaringInstance, ParseArguments());
- m_evaluated = true;
- m_isEvaluating = false;
- }
- catch (Exception e)
- {
- ExplorerCore.LogWarning($"Exception evaluating: {e.GetType()}, {e.Message}");
- ReflectionException = ReflectionHelpers.ExceptionToString(e);
- }
-
- if (ret != null)
- {
- //m_cachedReturnValue = CacheFactory.GetTypeAndCacheObject(ret);
- m_cachedReturnValue = CacheFactory.GetCacheObject(ret);
- m_cachedReturnValue.UpdateValue();
- }
- else
- {
- m_cachedReturnValue = null;
- }
- }
-
- private MethodInfo MakeGenericMethodFromInput()
- {
- var mi = MemInfo as MethodInfo;
-
- var list = new List();
- for (int i = 0; i < GenericArgs.Length; i++)
- {
- var input = GenericArgInput[i];
- if (ReflectionHelpers.GetTypeByName(input) is Type t)
- {
- if (GenericConstraints[i].Length == 0)
- {
- list.Add(t);
- }
- else
- {
- foreach (var constraint in GenericConstraints[i].Where(x => x != null))
- {
- if (!constraint.IsAssignableFrom(t))
- {
- ExplorerCore.LogWarning($"Generic argument #{i}, '{input}' is not assignable from the constraint '{constraint}'!");
- return null;
- }
- }
-
- list.Add(t);
- }
- }
- else
- {
- ExplorerCore.LogWarning($"Generic argument #{i}, could not get any type by the name of '{input}'!" +
- $" Make sure you use the full name, including the NameSpace.");
- return null;
- }
- }
-
- // make into a generic with type list
- mi = mi.MakeGenericMethod(list.ToArray());
-
- return mi;
- }
-
- // ==== GUI DRAW ====
-
- //public override void Draw(Rect window, float width)
- //{
- // base.Draw(window, width);
- //}
-
- public void DrawValue(Rect window, float width)
- {
- string typeLabel = $"{IValue.ValueType.FullName} ";
-
- if (m_evaluated)
- {
- if (m_cachedReturnValue != null)
- {
- m_cachedReturnValue.IValue.DrawValue(window, width);
- }
- else
- {
- GUILayout.Label($"null ({typeLabel})", new GUILayoutOption[0]);
- }
- }
- else
- {
- GUILayout.Label($"Not yet evaluated ({typeLabel})", new GUILayoutOption[0]);
- }
- }
-
- public void DrawGenericArgsInput()
- {
- GUILayout.Label($"Generic Arguments: ", new GUILayoutOption[0]);
-
- for (int i = 0; i < this.GenericArgs.Length; i++)
- {
- string types = "";
- if (this.GenericConstraints[i].Length > 0)
- {
- foreach (var constraint in this.GenericConstraints[i])
- {
- if (types != "") types += ", ";
-
- string type;
-
- if (constraint == null)
- type = "Any";
- else
- type = constraint.ToString();
-
- types += $"{type} ";
- }
- }
- else
- {
- types = $"Any ";
- }
- var input = this.GenericArgInput[i];
-
- GUIHelper.BeginHorizontal(new GUILayoutOption[0]);
-
- GUI.skin.label.alignment = TextAnchor.MiddleCenter;
- GUILayout.Label(
- $"{this.GenericArgs[i].Name} ",
- new GUILayoutOption[] { GUILayout.Width(15) }
- );
- this.GenericArgInput[i] = GUIHelper.TextField(input, new GUILayoutOption[] { GUILayout.Width(150) });
- GUI.skin.label.alignment = TextAnchor.MiddleLeft;
- GUILayout.Label(types, new GUILayoutOption[0]);
-
- GUILayout.EndHorizontal();
- }
- }
- }
-}
diff --git a/src/CacheObject/CacheObjectBase.cs b/src/CacheObject/CacheObjectBase.cs
deleted file mode 100644
index efba856..0000000
--- a/src/CacheObject/CacheObjectBase.cs
+++ /dev/null
@@ -1,117 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using UnityEngine;
-using Explorer.UI;
-using Explorer.UI.Shared;
-using Explorer.Helpers;
-
-namespace Explorer.CacheObject
-{
- public class CacheObjectBase
- {
- public InteractiveValue IValue;
-
- public virtual bool CanWrite => false;
- public virtual bool HasParameters => false;
- public virtual bool IsMember => false;
-
- public bool IsStaticClassSearchResult { get; set; }
-
- public virtual void Init(object obj, Type valueType)
- {
- if (valueType == null && obj == null)
- {
- return;
- }
-
- //ExplorerCore.Log("Initializing InteractiveValue of type " + valueType.FullName);
-
- InteractiveValue interactive;
-
- if (valueType == typeof(GameObject) || valueType == typeof(Transform))
- {
- interactive = new InteractiveGameObject();
- }
- else if (valueType == typeof(Texture2D))
- {
- interactive = new InteractiveTexture2D();
- }
- else if (valueType == typeof(Texture))
- {
- interactive = new InteractiveTexture();
- }
- else if (valueType == typeof(Sprite))
- {
- interactive = new InteractiveSprite();
- }
- else if (valueType.IsPrimitive || valueType == typeof(string))
- {
- interactive = new InteractivePrimitive();
- }
- else if (valueType.IsEnum)
- {
- if (valueType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] attributes && attributes.Length > 0)
- {
- interactive = new InteractiveFlags();
- }
- else
- {
- interactive = new InteractiveEnum();
- }
- }
- else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4))
- {
- interactive = new InteractiveVector();
- }
- else if (valueType == typeof(Quaternion))
- {
- interactive = new InteractiveQuaternion();
- }
- else if (valueType == typeof(Color))
- {
- interactive = new InteractiveColor();
- }
- else if (valueType == typeof(Rect))
- {
- interactive = new InteractiveRect();
- }
- // must check this before IsEnumerable
- else if (ReflectionHelpers.IsDictionary(valueType))
- {
- interactive = new InteractiveDictionary();
- }
- else if (ReflectionHelpers.IsEnumerable(valueType))
- {
- interactive = new InteractiveEnumerable();
- }
- else
- {
- interactive = new InteractiveValue();
- }
-
- interactive.Value = obj;
- interactive.ValueType = valueType;
-
- this.IValue = interactive;
- this.IValue.OwnerCacheObject = this;
-
- UpdateValue();
-
- this.IValue.Init();
- }
-
- public virtual void Draw(Rect window, float width)
- {
- IValue.Draw(window, width);
- }
-
- public virtual void UpdateValue()
- {
- IValue.UpdateValue();
- }
-
- public virtual void SetValue() => throw new NotImplementedException();
- }
-}
diff --git a/src/CacheObject/CacheProperty.cs b/src/CacheObject/CacheProperty.cs
deleted file mode 100644
index 2380d59..0000000
--- a/src/CacheObject/CacheProperty.cs
+++ /dev/null
@@ -1,84 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using System.Reflection;
-using Explorer.UI;
-using Explorer.Helpers;
-
-namespace Explorer.CacheObject
-{
- public class CacheProperty : CacheMember
- {
- public override bool IsStatic => (MemInfo as PropertyInfo).GetAccessors()[0].IsStatic;
-
- public override void InitMember(MemberInfo member, object declaringInstance)
- {
- base.InitMember(member, declaringInstance);
-
- var pi = member as PropertyInfo;
-
- this.m_arguments = pi.GetIndexParameters();
- this.m_argumentInput = new string[m_arguments.Length];
-
- base.Init(null, pi.PropertyType);
-
- UpdateValue();
- }
-
- public override void UpdateValue()
- {
- if (HasParameters && !m_isEvaluating)
- {
- // Need to enter parameters first.
- return;
- }
-
- if (IValue is InteractiveDictionary iDict)
- {
- if (!iDict.EnsureDictionaryIsSupported())
- {
- ReflectionException = "Not supported due to TypeInitializationException";
- return;
- }
- }
-
- try
- {
- var pi = MemInfo as PropertyInfo;
-
- if (pi.CanRead)
- {
- var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
-
- IValue.Value = pi.GetValue(target, ParseArguments());
-
- base.UpdateValue();
- }
- else // create a dummy value for Write-Only properties.
- {
- if (IValue.ValueType == typeof(string))
- {
- IValue.Value = "";
- }
- else
- {
- IValue.Value = Activator.CreateInstance(IValue.ValueType);
- }
- }
- }
- catch (Exception e)
- {
- ReflectionException = ReflectionHelpers.ExceptionToString(e);
- }
- }
-
- public override void SetValue()
- {
- var pi = MemInfo as PropertyInfo;
- var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
-
- pi.SetValue(target, IValue.Value, ParseArguments());
- }
- }
-}
diff --git a/src/Config/ModConfig.cs b/src/Config/ModConfig.cs
index 8c016f4..959fa1e 100644
--- a/src/Config/ModConfig.cs
+++ b/src/Config/ModConfig.cs
@@ -1,40 +1,43 @@
-using System.IO;
+using System;
+using System.IO;
using System.Xml.Serialization;
using UnityEngine;
-namespace Explorer.Config
+namespace UnityExplorer.Config
{
public class ModConfig
{
[XmlIgnore] public static readonly XmlSerializer Serializer = new XmlSerializer(typeof(ModConfig));
- [XmlIgnore] private const string EXPLORER_FOLDER = @"Mods\Explorer";
- [XmlIgnore] private const string SETTINGS_PATH = EXPLORER_FOLDER + @"\config.xml";
+ //[XmlIgnore] private const string EXPLORER_FOLDER = @"Mods\UnityExplorer";
+ [XmlIgnore] private const string SETTINGS_PATH = ExplorerCore.EXPLORER_FOLDER + @"\config.xml";
[XmlIgnore] public static ModConfig Instance;
// Actual configs
- public KeyCode Main_Menu_Toggle = KeyCode.F7;
- public Vector2 Default_Window_Size = new Vector2(550, 700);
- public int Default_Page_Limit = 20;
- public bool Bitwise_Support = false;
- public bool Tab_View = true;
- public string Default_Output_Path = @"Mods\Explorer";
+ public KeyCode Main_Menu_Toggle = KeyCode.F7;
+ public bool Force_Unlock_Mouse = true;
+ public int Default_Page_Limit = 25;
+ public string Default_Output_Path = ExplorerCore.EXPLORER_FOLDER;
+ public bool Log_Unity_Debug = false;
+ public bool Save_Logs_To_Disk = true;
+
+ public static event Action OnConfigChanged;
+
+ internal static void InvokeConfigChanged()
+ {
+ OnConfigChanged?.Invoke();
+ }
public static void OnLoad()
{
- if (!Directory.Exists(EXPLORER_FOLDER))
- {
- Directory.CreateDirectory(EXPLORER_FOLDER);
- }
-
- if (LoadSettings()) return;
+ if (LoadSettings())
+ return;
Instance = new ModConfig();
SaveSettings();
}
- // returns true if settings successfully loaded
public static bool LoadSettings()
{
if (!File.Exists(SETTINGS_PATH))
@@ -43,9 +46,7 @@ namespace Explorer.Config
try
{
using (var file = File.OpenRead(SETTINGS_PATH))
- {
Instance = (ModConfig)Serializer.Deserialize(file);
- }
}
catch
{
@@ -61,9 +62,7 @@ namespace Explorer.Config
File.Delete(SETTINGS_PATH);
using (var file = File.Create(SETTINGS_PATH))
- {
Serializer.Serialize(file, Instance);
- }
}
}
}
diff --git a/src/ExplorerBepInPlugin.cs b/src/ExplorerBepInPlugin.cs
index 83a4e24..cb0d780 100644
--- a/src/ExplorerBepInPlugin.cs
+++ b/src/ExplorerBepInPlugin.cs
@@ -7,72 +7,71 @@ using BepInEx.Logging;
using HarmonyLib;
using UnityEngine;
using UnityEngine.SceneManagement;
+using UnityEngine.UI;
+using UnityExplorer.UI.Modules;
#if CPP
using UnhollowerRuntimeLib;
using BepInEx.IL2CPP;
#endif
-namespace Explorer
+namespace UnityExplorer
{
- [BepInPlugin(ExplorerCore.GUID, "Explorer", ExplorerCore.VERSION)]
-#if CPP
- public class ExplorerBepInPlugin : BasePlugin
-#else
+#if MONO
+ [BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
public class ExplorerBepInPlugin : BaseUnityPlugin
-#endif
{
public static ExplorerBepInPlugin Instance;
- public static ManualLogSource Logging =>
-#if CPP
- Instance?.Log;
-#else
- Instance?.Logger;
-#endif
+ public static ManualLogSource Logging => Instance?.Logger;
public static readonly Harmony HarmonyInstance = new Harmony(ExplorerCore.GUID);
-#if CPP
- // temporary for Il2Cpp until scene change delegate works
- private static string lastSceneName;
-#endif
-
- // Init
-#if CPP
- public override void Load()
- {
-#else
internal void Awake()
{
-#endif
Instance = this;
+ new ExplorerCore();
+
+ // HarmonyInstance.PatchAll();
+ }
+
+ internal void Update()
+ {
+ ExplorerCore.Update();
+ }
+ }
+#endif
+
#if CPP
+ [BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
+ public class ExplorerBepInPlugin : BasePlugin
+ {
+ public static ExplorerBepInPlugin Instance;
+
+ public static ManualLogSource Logging => Instance?.Log;
+
+ public static readonly Harmony HarmonyInstance = new Harmony(ExplorerCore.GUID);
+
+ // Init
+ public override void Load()
+ {
+ Instance = this;
+
ClassInjector.RegisterTypeInIl2Cpp();
var obj = new GameObject(
"ExplorerBehaviour",
- new Il2CppSystem.Type[]
- {
- Il2CppType.Of()
- }
+ new Il2CppSystem.Type[] { Il2CppType.Of() }
);
+ obj.hideFlags = HideFlags.HideAndDontSave;
GameObject.DontDestroyOnLoad(obj);
-#else
- SceneManager.activeSceneChanged += DoSceneChange;
-#endif
new ExplorerCore();
- //HarmonyInstance.PatchAll();
+ // HarmonyInstance.PatchAll();
}
- internal static void DoSceneChange(Scene arg0, Scene arg1)
- {
- ExplorerCore.OnSceneChange();
- }
-
-#if CPP // BepInEx Il2Cpp mod class doesn't have monobehaviour methods yet, so wrap them in a dummy.
+ // BepInEx Il2Cpp mod class doesn't have monobehaviour methods yet, so wrap them in a dummy.
public class ExplorerBehaviour : MonoBehaviour
{
public ExplorerBehaviour(IntPtr ptr) : base(ptr) { }
@@ -82,28 +81,12 @@ namespace Explorer
Logging.LogMessage("ExplorerBehaviour.Awake");
}
-#endif
internal void Update()
{
ExplorerCore.Update();
-
-#if CPP
- var scene = SceneManager.GetActiveScene();
- if (scene.name != lastSceneName)
- {
- lastSceneName = scene.name;
- DoSceneChange(scene, scene);
- }
-#endif
}
-
- internal void OnGUI()
- {
- ExplorerCore.OnGUI();
- }
-#if CPP
}
-#endif
}
+#endif
}
#endif
diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs
index 2dc3e96..b28b4df 100644
--- a/src/ExplorerCore.cs
+++ b/src/ExplorerCore.cs
@@ -1,36 +1,36 @@
-using System.Collections;
-using System.Linq;
-using Explorer.Config;
-using Explorer.UI;
-using Explorer.UI.Inspectors;
-using Explorer.UI.Main;
-using Explorer.UI.Shared;
+using System;
+using UnityExplorer.Config;
+using UnityExplorer.Input;
+using UnityExplorer.UI;
+using UnityExplorer.UI.Modules;
using UnityEngine;
+using UnityExplorer.Inspectors;
+using System.IO;
+using UnityExplorer.Unstrip;
+using UnityEngine.SceneManagement;
-namespace Explorer
+namespace UnityExplorer
{
public class ExplorerCore
{
- public const string NAME = "Explorer " + VERSION + " (" + PLATFORM + ", " + MODLOADER + ")";
- public const string VERSION = "2.1.0";
- public const string AUTHOR = "Sinai";
- public const string GUID = "com.sinai.explorer";
-
- public const string PLATFORM =
-#if CPP
- "Il2Cpp";
-#else
- "Mono";
-#endif
- public const string MODLOADER =
-#if ML
- "MelonLoader";
-#else
- "BepInEx";
-#endif
+ public const string NAME = "UnityExplorer";
+ public const string VERSION = "3.0.0";
+ public const string AUTHOR = "Sinai";
+ public const string GUID = "com.sinai.unityexplorer";
+ public const string EXPLORER_FOLDER = @"Mods\UnityExplorer";
public static ExplorerCore Instance { get; private set; }
+ public static bool ShowMenu
+ {
+ get => s_showMenu;
+ set => SetShowMenu(value);
+ }
+ public static bool s_showMenu;
+
+ private static bool s_doneUIInit;
+ private static float s_timeSinceStartup;
+
public ExplorerCore()
{
if (Instance != null)
@@ -41,76 +41,129 @@ namespace Explorer
Instance = this;
- ModConfig.OnLoad();
+ if (!Directory.Exists(EXPLORER_FOLDER))
+ Directory.CreateDirectory(EXPLORER_FOLDER);
- new MainMenu();
- new WindowManager();
+ ModConfig.OnLoad();
InputManager.Init();
ForceUnlockCursor.Init();
+ SetupEvents();
+
ShowMenu = true;
- Log($"{NAME} initialized.");
- }
-
- public static bool ShowMenu
- {
- get => m_showMenu;
- set => SetShowMenu(value);
- }
- public static bool m_showMenu;
-
- private static void SetShowMenu(bool show)
- {
- m_showMenu = show;
- ForceUnlockCursor.UpdateCursorControl();
+ Log($"{NAME} {VERSION} initialized.");
}
public static void Update()
{
- if (InputManager.GetKeyDown(ModConfig.Instance.Main_Menu_Toggle))
- {
- ShowMenu = !ShowMenu;
- }
+ if (!s_doneUIInit)
+ CheckUIInit();
- if (ShowMenu)
+ if (MouseInspector.Enabled)
+ MouseInspector.UpdateInspect();
+ else
{
- ForceUnlockCursor.Update();
- InspectUnderMouse.Update();
+ if (InputManager.GetKeyDown(ModConfig.Instance.Main_Menu_Toggle))
+ ShowMenu = !ShowMenu;
- MainMenu.Instance.Update();
- WindowManager.Instance.Update();
+ if (ShowMenu && s_doneUIInit)
+ UIManager.Update();
}
}
- public static void OnGUI()
+ private static void CheckUIInit()
{
- if (!ShowMenu) return;
+ s_timeSinceStartup += Time.deltaTime;
- var origSkin = GUI.skin;
- GUI.skin = UIStyles.WindowSkin;
-
- MainMenu.Instance.OnGUI();
- WindowManager.Instance.OnGUI();
- InspectUnderMouse.OnGUI();
-
- if (!ResizeDrag.IsMouseInResizeArea && WindowManager.IsMouseInWindow)
+ if (s_timeSinceStartup > 0.1f)
{
- InputManager.ResetInputAxes();
+ s_doneUIInit = true;
+ try
+ {
+ UIManager.Init();
+ Log("Initialized UnityExplorer UI.");
+
+ // temp debug
+ InspectorManager.Instance.Inspect(Tests.TestClass.Instance);
+ }
+ catch (Exception e)
+ {
+ LogWarning($"Exception setting up UI: {e}");
+ }
+ }
+ }
+
+ private void SetupEvents()
+ {
+#if CPP
+ try
+ {
+ Application.add_logMessageReceived(new Action(OnUnityLog));
+ SceneManager.add_sceneLoaded(new Action((Scene a, LoadSceneMode b) => { OnSceneLoaded(); }));
+ SceneManager.add_activeSceneChanged(new Action((Scene a, Scene b) => { OnSceneLoaded(); }));
+ }
+ catch { }
+#else
+ Application.logMessageReceived += OnUnityLog;
+ SceneManager.sceneLoaded += (Scene a, LoadSceneMode b) => { OnSceneLoaded(); };
+ SceneManager.activeSceneChanged += (Scene a, Scene b) => { OnSceneLoaded(); };
+#endif
+ }
+
+ internal void OnSceneLoaded()
+ {
+ UIManager.OnSceneChange();
+ }
+
+ private static void SetShowMenu(bool show)
+ {
+ s_showMenu = show;
+
+ if (UIManager.CanvasRoot)
+ {
+ UIManager.CanvasRoot.SetActive(show);
+
+ if (show)
+ ForceUnlockCursor.SetEventSystem();
+ else
+ ForceUnlockCursor.ReleaseEventSystem();
}
- GUI.skin = origSkin;
+ ForceUnlockCursor.UpdateCursorControl();
}
- public static void OnSceneChange()
+ private void OnUnityLog(string message, string stackTrace, LogType type)
{
- ScenePage.Instance?.OnSceneChange();
- SearchPage.Instance?.OnSceneChange();
+ if (!DebugConsole.LogUnity)
+ return;
+
+ message = $"[UNITY] {message}";
+
+ switch (type)
+ {
+ case LogType.Assert:
+ case LogType.Log:
+ Log(message, true);
+ break;
+ case LogType.Warning:
+ LogWarning(message, true);
+ break;
+ case LogType.Exception:
+ case LogType.Error:
+ LogError(message, true);
+ break;
+ }
}
- public static void Log(object message)
+ public static void Log(object message, bool unity = false)
{
+ DebugConsole.Log(message?.ToString());
+
+ if (unity)
+ return;
+
#if ML
MelonLoader.MelonLogger.Log(message?.ToString());
#else
@@ -118,22 +171,43 @@ namespace Explorer
#endif
}
- public static void LogWarning(object message)
+ public static void LogWarning(object message, bool unity = false)
{
+ DebugConsole.Log(message?.ToString(), "FFFF00");
+
+ if (unity)
+ return;
+
#if ML
MelonLoader.MelonLogger.LogWarning(message?.ToString());
#else
- ExplorerBepInPlugin.Logging?.LogWarning(message?.ToString());
+ ExplorerBepInPlugin.Logging?.LogWarning(message?.ToString());
#endif
}
- public static void LogError(object message)
+ public static void LogError(object message, bool unity = false)
{
+ DebugConsole.Log(message?.ToString(), "FF0000");
+
+ if (unity)
+ return;
+
#if ML
MelonLoader.MelonLogger.LogError(message?.ToString());
#else
- ExplorerBepInPlugin.Logging?.LogError(message?.ToString());
+ ExplorerBepInPlugin.Logging?.LogError(message?.ToString());
#endif
}
+
+
+ public static string RemoveInvalidFilenameChars(string s)
+ {
+ var invalid = System.IO.Path.GetInvalidFileNameChars();
+ foreach (var c in invalid)
+ {
+ s = s.Replace(c.ToString(), "");
+ }
+ return s;
+ }
}
}
diff --git a/src/ExplorerMelonMod.cs b/src/ExplorerMelonMod.cs
index 3af2a2d..9525009 100644
--- a/src/ExplorerMelonMod.cs
+++ b/src/ExplorerMelonMod.cs
@@ -1,11 +1,8 @@
#if ML
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
using MelonLoader;
-namespace Explorer
+namespace UnityExplorer
{
public class ExplorerMelonMod : MelonMod
{
@@ -18,19 +15,14 @@ namespace Explorer
new ExplorerCore();
}
- public override void OnLevelWasLoaded(int level)
- {
- ExplorerCore.OnSceneChange();
- }
-
public override void OnUpdate()
{
ExplorerCore.Update();
}
- public override void OnGUI()
+ public override void OnLevelWasLoaded(int level)
{
- ExplorerCore.OnGUI();
+ ExplorerCore.Instance.OnSceneLoaded();
}
}
}
diff --git a/src/Extensions/ReflectionExtensions.cs b/src/Extensions/ReflectionExtensions.cs
deleted file mode 100644
index d8422f5..0000000
--- a/src/Extensions/ReflectionExtensions.cs
+++ /dev/null
@@ -1,41 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using Explorer.Helpers;
-
-namespace Explorer
-{
- public static class ReflectionExtensions
- {
-#if CPP
- public static object Il2CppCast(this object obj, Type castTo)
- {
- return ReflectionHelpers.Il2CppCast(obj, castTo);
- }
-#endif
-
- public static IEnumerable TryGetTypes(this Assembly asm)
- {
- try
- {
- return asm.GetTypes();
- }
- catch (ReflectionTypeLoadException e)
- {
- try
- {
- return asm.GetExportedTypes();
- }
- catch
- {
- return e.Types.Where(t => t != null);
- }
- }
- catch
- {
- return Enumerable.Empty();
- }
- }
- }
-}
diff --git a/src/Extensions/UnityExtensions.cs b/src/Extensions/UnityExtensions.cs
deleted file mode 100644
index c1cc29c..0000000
--- a/src/Extensions/UnityExtensions.cs
+++ /dev/null
@@ -1,24 +0,0 @@
-using UnityEngine;
-
-namespace Explorer
-{
- public static class UnityExtensions
- {
- public static string GetGameObjectPath(this Transform _transform)
- {
- return GetGameObjectPath(_transform, true);
- }
-
- public static string GetGameObjectPath(this Transform _transform, bool _includeThisName)
- {
- string path = _includeThisName ? ("/" + _transform.name) : "";
- GameObject gameObject = _transform.gameObject;
- while (gameObject.transform.parent != null)
- {
- gameObject = gameObject.transform.parent.gameObject;
- path = "/" + gameObject.name + path;
- }
- return path;
- }
- }
-}
diff --git a/src/Helpers/EventHelper.cs b/src/Helpers/EventHelper.cs
new file mode 100644
index 0000000..375ebee
--- /dev/null
+++ b/src/Helpers/EventHelper.cs
@@ -0,0 +1,33 @@
+#if CPP
+using System;
+using UnityEngine.Events;
+
+namespace UnityExplorer.Helpers
+{
+ // Possibly temporary, just so Il2Cpp can do the same style "AddListener" as Mono.
+ // Just saves me having a preprocessor directive for every single AddListener.
+
+ public static class EventHelper
+ {
+ public static void AddListener(this UnityEvent action, Action listener)
+ {
+ action.AddListener(listener);
+ }
+
+ public static void AddListener(this UnityEvent action, Action listener)
+ {
+ action.AddListener(listener);
+ }
+
+ public static void AddListener(this UnityEvent action, Action listener)
+ {
+ action.AddListener(listener);
+ }
+
+ public static void AddListener(this UnityEvent action, Action listener)
+ {
+ action.AddListener(listener);
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/src/Helpers/ICallHelper.cs b/src/Helpers/ICallHelper.cs
index bc89879..11f5f33 100644
--- a/src/Helpers/ICallHelper.cs
+++ b/src/Helpers/ICallHelper.cs
@@ -1,13 +1,10 @@
#if CPP
using System;
using System.Collections.Generic;
-using System.Linq;
-using System.Runtime.InteropServices;
-using System.Text;
-using System.Reflection;
using System.Diagnostics.CodeAnalysis;
+using System.Runtime.InteropServices;
-namespace Explorer.Helpers
+namespace UnityExplorer.Helpers
{
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")]
public static class ICallHelper
@@ -17,18 +14,16 @@ namespace Explorer.Helpers
public static T GetICall(string iCallName) where T : Delegate
{
if (iCallCache.ContainsKey(iCallName))
- {
return (T)iCallCache[iCallName];
- }
- var ptr = il2cpp_resolve_icall(iCallName);
+ IntPtr ptr = il2cpp_resolve_icall(iCallName);
if (ptr == IntPtr.Zero)
{
throw new MissingMethodException($"Could not resolve internal call by name '{iCallName}'!");
}
- var iCall = Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
+ Delegate iCall = Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
iCallCache.Add(iCallName, iCall);
return (T)iCall;
diff --git a/src/Helpers/ReflectionHelpers.cs b/src/Helpers/ReflectionHelpers.cs
index 793610a..d47920b 100644
--- a/src/Helpers/ReflectionHelpers.cs
+++ b/src/Helpers/ReflectionHelpers.cs
@@ -2,86 +2,25 @@
using System.Collections;
using System.Collections.Generic;
using System.IO;
+using System.Linq;
using System.Reflection;
using UnityEngine;
using BF = System.Reflection.BindingFlags;
using System.Diagnostics.CodeAnalysis;
#if CPP
-using ILType = Il2CppSystem.Type;
+using CppType = Il2CppSystem.Type;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using System.Runtime.InteropServices;
#endif
-namespace Explorer.Helpers
+namespace UnityExplorer.Helpers
{
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")]
- public class ReflectionHelpers
+ public static class ReflectionHelpers
{
public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
-#if CPP
- public static ILType GameObjectType => Il2CppType.Of();
- public static ILType TransformType => Il2CppType.Of();
- public static ILType ObjectType => Il2CppType.Of();
- public static ILType ComponentType => Il2CppType.Of();
- public static ILType BehaviourType => Il2CppType.Of();
-#else
- public static Type GameObjectType => typeof(GameObject);
- public static Type TransformType => typeof(Transform);
- public static Type ObjectType => typeof(UnityEngine.Object);
- public static Type ComponentType => typeof(Component);
- public static Type BehaviourType => typeof(Behaviour);
-#endif
-
-#if CPP
- private static readonly Dictionary ClassPointers = new Dictionary();
-
- public static object Il2CppCast(object obj, Type castTo)
- {
- if (!(obj is Il2CppSystem.Object ilObj))
- return obj;
-
- if (!typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
- return obj as System.Object;
-
- IntPtr castToPtr;
- if (!ClassPointers.ContainsKey(castTo))
- {
- castToPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
- .MakeGenericType(new Type[] { castTo })
- .GetField("NativeClassPtr", BF.Public | BF.Static)
- .GetValue(null);
-
- ClassPointers.Add(castTo, castToPtr);
- }
- else
- {
- castToPtr = ClassPointers[castTo];
- }
-
- if (castToPtr == IntPtr.Zero)
- return obj;
-
- var classPtr = il2cpp_object_get_class(ilObj.Pointer);
-
- if (!il2cpp_class_is_assignable_from(castToPtr, classPtr))
- return obj;
-
- if (RuntimeSpecificsStore.IsInjected(castToPtr))
- return UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(ilObj.Pointer);
-
- return Activator.CreateInstance(castTo, ilObj.Pointer);
- }
-
- [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
- public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass);
-
- [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
- public static extern IntPtr il2cpp_object_get_class(IntPtr obj);
-
-#endif
-
public static Type GetTypeByName(string fullName)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
@@ -98,33 +37,11 @@ namespace Explorer.Helpers
return null;
}
- public static Type GetActualType(object obj)
- {
- if (obj == null) return null;
-
-#if CPP
- // Need to use GetIl2CppType for Il2CppSystem Objects
- if (obj is Il2CppSystem.Object ilObject)
- {
- // Prevent weird behaviour when inspecting an Il2CppSystem.Type object.
- if (ilObject is ILType)
- {
- return typeof(ILType);
- }
-
- return Type.GetType(ilObject.GetIl2CppType().AssemblyQualifiedName) ?? obj.GetType();
- }
-#endif
-
- // It's a normal object, this is fine
- return obj.GetType();
- }
-
public static Type[] GetAllBaseTypes(object obj) => GetAllBaseTypes(GetActualType(obj));
public static Type[] GetAllBaseTypes(Type type)
{
- var list = new List();
+ List list = new List();
while (type != null)
{
@@ -135,15 +52,147 @@ namespace Explorer.Helpers
return list.ToArray();
}
+ public static Type GetActualType(object obj)
+ {
+ if (obj == null)
+ return null;
+
+ var type = obj.GetType();
+#if CPP
+ if (obj is Il2CppSystem.Object ilObject)
+ {
+ if (ilObject is CppType)
+ return typeof(CppType);
+
+ if (!string.IsNullOrEmpty(type.Namespace))
+ {
+ // Il2CppSystem-namespace objects should just return GetType,
+ // because using GetIl2CppType returns the System namespace type instead.
+ if (type.Namespace.StartsWith("System.") || type.Namespace.StartsWith("Il2CppSystem."))
+ return ilObject.GetType();
+ }
+
+ var il2cppType = ilObject.GetIl2CppType();
+
+ // check if type is injected
+ IntPtr classPtr = il2cpp_object_get_class(ilObject.Pointer);
+ if (RuntimeSpecificsStore.IsInjected(classPtr))
+ {
+ var typeByName = GetTypeByName(il2cppType.FullName);
+ if (typeByName != null)
+ return typeByName;
+ }
+
+ // this should be fine for all other il2cpp objects
+ var getType = GetMonoType(il2cppType);
+ if (getType != null)
+ return getType;
+ }
+#endif
+ return type;
+ }
+
+#if CPP
+ private static readonly Dictionary Il2CppToMonoType = new Dictionary();
+
+ public static Type GetMonoType(CppType cppType)
+ {
+ if (Il2CppToMonoType.ContainsKey(cppType))
+ return Il2CppToMonoType[cppType];
+
+ var getType = Type.GetType(cppType.AssemblyQualifiedName);
+ Il2CppToMonoType.Add(cppType, getType);
+ return getType;
+ }
+
+ private static readonly Dictionary ClassPointers = new Dictionary();
+
+ public static object Il2CppCast(this object obj, Type castTo)
+ {
+ if (!(obj is Il2CppSystem.Object ilObj))
+ {
+ return obj;
+ }
+
+ if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr))
+ return obj;
+
+ IntPtr classPtr = il2cpp_object_get_class(ilObj.Pointer);
+
+ if (!il2cpp_class_is_assignable_from(castToPtr, classPtr))
+ return obj;
+
+ if (RuntimeSpecificsStore.IsInjected(castToPtr))
+ return UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(ilObj.Pointer);
+
+ return Activator.CreateInstance(castTo, ilObj.Pointer);
+ }
+
+ public static bool Il2CppTypeNotNull(Type type)
+ {
+ return Il2CppTypeNotNull(type, out _);
+ }
+
+ public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr)
+ {
+ if (!ClassPointers.ContainsKey(type))
+ {
+ il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
+ .MakeGenericType(new Type[] { type })
+ .GetField("NativeClassPtr", BF.Public | BF.Static)
+ .GetValue(null);
+
+ ClassPointers.Add(type, il2cppPtr);
+ }
+ else
+ il2cppPtr = ClassPointers[type];
+
+ return il2cppPtr != IntPtr.Zero;
+ }
+
+ [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass);
+
+ [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ public static extern IntPtr il2cpp_object_get_class(IntPtr obj);
+
+#endif
+
+ public static IEnumerable TryGetTypes(this Assembly asm)
+ {
+ try
+ {
+ return asm.GetTypes();
+ }
+ catch (ReflectionTypeLoadException e)
+ {
+ try
+ {
+ return asm.GetExportedTypes();
+ }
+ catch
+ {
+ return e.Types.Where(t => t != null);
+ }
+ }
+ catch
+ {
+ return Enumerable.Empty();
+ }
+ }
+
public static bool LoadModule(string module)
{
#if CPP
#if ML
- var path = $@"MelonLoader\Managed\{module}.dll";
+ string path = $@"MelonLoader\Managed\{module}.dll";
#else
var path = $@"BepInEx\unhollowed\{module}.dll";
#endif
- if (!File.Exists(path)) return false;
+ if (!File.Exists(path))
+ {
+ return false;
+ }
try
{
@@ -204,8 +253,17 @@ namespace Explorer.Helpers
#endif
}
- public static string ExceptionToString(Exception e)
+ public static string ExceptionToString(Exception e, bool innerMost = false)
{
+ while (innerMost && e.InnerException != null)
+ {
+#if CPP
+ if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException runtimeEx)
+ break;
+#endif
+ e = e.InnerException;
+ }
+
return e.GetType() + ", " + e.Message;
}
}
diff --git a/src/Helpers/Texture2DHelpers.cs b/src/Helpers/Texture2DHelpers.cs
index 2d5f4b0..a05e9e5 100644
--- a/src/Helpers/Texture2DHelpers.cs
+++ b/src/Helpers/Texture2DHelpers.cs
@@ -1,15 +1,12 @@
using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
using UnityEngine;
using System.IO;
using System.Reflection;
#if CPP
-using Explorer.Unstrip.ImageConversion;
+using UnityExplorer.Unstrip;
#endif
-namespace Explorer.Helpers
+namespace UnityExplorer.Helpers
{
public static class Texture2DHelpers
{
@@ -56,14 +53,12 @@ namespace Explorer.Helpers
}
}
- public static Texture2D Copy(Texture2D orig, Rect rect, bool isDTXnmNormal = false)
+ public static Texture2D Copy(Texture2D orig, Rect rect) //, bool isDTXnmNormal = false)
{
Color[] pixels;
if (!orig.IsReadable())
- {
orig = ForceReadTexture(orig);
- }
pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
@@ -77,7 +72,7 @@ namespace Explorer.Helpers
{
try
{
- var origFilter = tex.filterMode;
+ FilterMode origFilter = tex.filterMode;
tex.filterMode = FilterMode.Point;
var rt = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.ARGB32);
@@ -105,12 +100,10 @@ namespace Explorer.Helpers
public static void SaveTextureAsPNG(Texture2D tex, string dir, string name, bool isDTXnmNormal = false)
{
if (!Directory.Exists(dir))
- {
Directory.CreateDirectory(dir);
- }
byte[] data;
- var savepath = dir + @"\" + name + ".png";
+ string savepath = dir + @"\" + name + ".png";
// Make sure we can EncodeToPNG it.
if (tex.format != TextureFormat.ARGB32 || !tex.IsReadable())
@@ -154,12 +147,12 @@ namespace Explorer.Helpers
for (int i = 0; i < colors.Length; i++)
{
- Color c = colors[i];
+ var c = colors[i];
c.r = c.a * 2 - 1; // red <- alpha
c.g = c.g * 2 - 1; // green is always the same
- Vector2 rg = new Vector2(c.r, c.g); //this is the red-green vector
+ var rg = new Vector2(c.r, c.g); //this is the red-green vector
c.b = Mathf.Sqrt(1 - Mathf.Clamp01(Vector2.Dot(rg, rg))); //recalculate the blue channel
colors[i] = new Color(
diff --git a/src/Helpers/UnityHelpers.cs b/src/Helpers/UnityHelpers.cs
index 5bad857..b1a3d8c 100644
--- a/src/Helpers/UnityHelpers.cs
+++ b/src/Helpers/UnityHelpers.cs
@@ -1,8 +1,8 @@
using UnityEngine;
-namespace Explorer.Helpers
+namespace UnityExplorer.Helpers
{
- public class UnityHelpers
+ public static class UnityHelpers
{
private static Camera m_mainCamera;
@@ -18,12 +18,45 @@ namespace Explorer.Helpers
}
}
- public static string ActiveSceneName
+ public static bool IsNullOrDestroyed(this object obj, bool suppressWarning = true)
{
- get
+ var unityObj = obj as Object;
+ if (obj == null)
{
- return UnityEngine.SceneManagement.SceneManager.GetActiveScene().name;
+ if (!suppressWarning)
+ ExplorerCore.LogWarning("The target instance is null!");
+
+ return true;
}
+ else if (obj is Object)
+ {
+ if (!unityObj)
+ {
+ if (!suppressWarning)
+ ExplorerCore.LogWarning("The target UnityEngine.Object was destroyed!");
+
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public static string ToStringLong(this Vector3 vec)
+ {
+ return $"{vec.x:F3}, {vec.y:F3}, {vec.z:F3}";
+ }
+
+ public static string GetTransformPath(this Transform t, bool includeThisName = false)
+ {
+ string path = includeThisName ? t.transform.name : "";
+
+ while (t.parent != null)
+ {
+ t = t.parent;
+ path = $"{t.name}/{path}";
+ }
+
+ return path;
}
}
}
diff --git a/src/Input/IAbstractInput.cs b/src/Input/IHandleInput.cs
similarity index 52%
rename from src/Input/IAbstractInput.cs
rename to src/Input/IHandleInput.cs
index ad33315..a2e9d8e 100644
--- a/src/Input/IAbstractInput.cs
+++ b/src/Input/IHandleInput.cs
@@ -1,15 +1,9 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using UnityEngine;
+using UnityEngine;
-namespace Explorer.Input
+namespace UnityExplorer.Input
{
- public interface IAbstractInput
+ public interface IHandleInput
{
- void Init();
-
Vector2 MousePosition { get; }
bool GetKeyDown(KeyCode key);
diff --git a/src/Input/InputManager.cs b/src/Input/InputManager.cs
index 291a281..127d495 100644
--- a/src/Input/InputManager.cs
+++ b/src/Input/InputManager.cs
@@ -1,19 +1,16 @@
using System;
-using System.Reflection;
using UnityEngine;
-using Explorer.Input;
-using Explorer.Helpers;
+using UnityExplorer.Helpers;
using System.Diagnostics.CodeAnalysis;
#if CPP
using UnhollowerBaseLib;
#endif
-namespace Explorer
+namespace UnityExplorer.Input
{
- [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Unity style")]
public static class InputManager
{
- private static IAbstractInput m_inputModule;
+ private static IHandleInput m_inputModule;
public static void Init()
{
@@ -31,8 +28,6 @@ namespace Explorer
ExplorerCore.LogWarning("Could not find any Input module!");
m_inputModule = new NoInput();
}
-
- m_inputModule.Init();
}
public static Vector3 MousePosition => m_inputModule.MousePosition;
@@ -42,47 +37,5 @@ namespace Explorer
public static bool GetMouseButtonDown(int btn) => m_inputModule.GetMouseButtonDown(btn);
public static bool GetMouseButton(int btn) => m_inputModule.GetMouseButton(btn);
-
-#if CPP
- internal delegate void d_ResetInputAxes();
- public static void ResetInputAxes() => ICallHelper.GetICall("UnityEngine.Input::ResetInputAxes").Invoke();
-#else
- public static void ResetInputAxes() => UnityEngine.Input.ResetInputAxes();
-#endif
-
-#if CPP
- // public extern static string compositionString { get; }
-
- internal delegate IntPtr d_get_compositionString();
-
- public static string compositionString
- {
- get
- {
- var iCall = ICallHelper.GetICall("UnityEngine.Input::get_compositionString");
- return IL2CPP.Il2CppStringToManaged(iCall.Invoke());
- }
- }
-
- // public extern static Vector2 compositionCursorPos { get; set; }
-
- internal delegate void d_get_compositionCursorPos(out Vector2 ret);
- internal delegate void d_set_compositionCursorPos(ref Vector2 value);
-
- public static Vector2 compositionCursorPos
- {
- get
- {
- var iCall = ICallHelper.GetICall("UnityEngine.Input::get_compositionCursorPos_Injected");
- iCall.Invoke(out Vector2 ret);
- return ret;
- }
- set
- {
- var iCall = ICallHelper.GetICall("UnityEngine.Input::set_compositionCursorPos_Injected");
- iCall.Invoke(ref value);
- }
- }
-#endif
}
}
\ No newline at end of file
diff --git a/src/Input/InputSystem.cs b/src/Input/InputSystem.cs
index d653004..79157a9 100644
--- a/src/Input/InputSystem.cs
+++ b/src/Input/InputSystem.cs
@@ -1,15 +1,35 @@
using System;
using System.Reflection;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
+using UnityExplorer.Helpers;
using UnityEngine;
-using Explorer.Helpers;
-namespace Explorer.Input
+namespace UnityExplorer.Input
{
- public class InputSystem : IAbstractInput
+ public class InputSystem : IHandleInput
{
+ public InputSystem()
+ {
+ ExplorerCore.Log("Initializing new InputSystem support...");
+
+ m_kbCurrentProp = TKeyboard.GetProperty("current");
+ m_kbIndexer = TKeyboard.GetProperty("Item", new Type[] { TKey });
+
+ var btnControl = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Controls.ButtonControl");
+ m_btnIsPressedProp = btnControl.GetProperty("isPressed");
+ m_btnWasPressedProp = btnControl.GetProperty("wasPressedThisFrame");
+
+ m_mouseCurrentProp = TMouse.GetProperty("current");
+ m_leftButtonProp = TMouse.GetProperty("leftButton");
+ m_rightButtonProp = TMouse.GetProperty("rightButton");
+
+ m_positionProp = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Pointer")
+ .GetProperty("position");
+
+ m_readVector2InputMethod = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.InputControl`1")
+ .MakeGenericType(typeof(Vector2))
+ .GetMethod("ReadValue");
+ }
+
public static Type TKeyboard => m_tKeyboard ?? (m_tKeyboard = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Keyboard"));
private static Type m_tKeyboard;
@@ -83,28 +103,5 @@ namespace Explorer.Input
default: throw new NotImplementedException();
}
}
-
- public void Init()
- {
- ExplorerCore.Log("Initializing new InputSystem support...");
-
- m_kbCurrentProp = TKeyboard.GetProperty("current");
- m_kbIndexer = TKeyboard.GetProperty("Item", new Type[] { TKey });
-
- var btnControl = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Controls.ButtonControl");
- m_btnIsPressedProp = btnControl.GetProperty("isPressed");
- m_btnWasPressedProp = btnControl.GetProperty("wasPressedThisFrame");
-
- m_mouseCurrentProp = TMouse.GetProperty("current");
- m_leftButtonProp = TMouse.GetProperty("leftButton");
- m_rightButtonProp = TMouse.GetProperty("rightButton");
-
- m_positionProp = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.Pointer")
- .GetProperty("position");
-
- m_readVector2InputMethod = ReflectionHelpers.GetTypeByName("UnityEngine.InputSystem.InputControl`1")
- .MakeGenericType(typeof(Vector2))
- .GetMethod("ReadValue");
- }
}
}
diff --git a/src/Input/LegacyInput.cs b/src/Input/LegacyInput.cs
index 4188316..d4dc042 100644
--- a/src/Input/LegacyInput.cs
+++ b/src/Input/LegacyInput.cs
@@ -1,15 +1,23 @@
using System;
using System.Reflection;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
+using UnityExplorer.Helpers;
using UnityEngine;
-using Explorer.Helpers;
-namespace Explorer.Input
+namespace UnityExplorer.Input
{
- public class LegacyInput : IAbstractInput
+ public class LegacyInput : IHandleInput
{
+ public LegacyInput()
+ {
+ ExplorerCore.Log("Initializing Legacy Input support...");
+
+ m_mousePositionProp = TInput.GetProperty("mousePosition");
+ m_getKeyMethod = TInput.GetMethod("GetKey", new Type[] { typeof(KeyCode) });
+ m_getKeyDownMethod = TInput.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) });
+ m_getMouseButtonMethod = TInput.GetMethod("GetMouseButton", new Type[] { typeof(int) });
+ m_getMouseButtonDownMethod = TInput.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) });
+ }
+
public static Type TInput => m_tInput ?? (m_tInput = ReflectionHelpers.GetTypeByName("UnityEngine.Input"));
private static Type m_tInput;
@@ -28,16 +36,5 @@ namespace Explorer.Input
public bool GetMouseButton(int btn) => (bool)m_getMouseButtonMethod.Invoke(null, new object[] { btn });
public bool GetMouseButtonDown(int btn) => (bool)m_getMouseButtonDownMethod.Invoke(null, new object[] { btn });
-
- public void Init()
- {
- ExplorerCore.Log("Initializing Legacy Input support...");
-
- m_mousePositionProp = TInput.GetProperty("mousePosition");
- m_getKeyMethod = TInput.GetMethod("GetKey", new Type[] { typeof(KeyCode) });
- m_getKeyDownMethod = TInput.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) });
- m_getMouseButtonMethod = TInput.GetMethod("GetMouseButton", new Type[] { typeof(int) });
- m_getMouseButtonDownMethod = TInput.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) });
- }
}
}
diff --git a/src/Input/NoInput.cs b/src/Input/NoInput.cs
index a5b2297..7fe18a7 100644
--- a/src/Input/NoInput.cs
+++ b/src/Input/NoInput.cs
@@ -1,14 +1,10 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-using UnityEngine;
+using UnityEngine;
-namespace Explorer.Input
+namespace UnityExplorer.Input
{
// Just a stub for games where no Input module was able to load at all.
- public class NoInput : IAbstractInput
+ public class NoInput : IHandleInput
{
public Vector2 MousePosition => Vector2.zero;
@@ -17,7 +13,5 @@ namespace Explorer.Input
public bool GetMouseButton(int btn) => false;
public bool GetMouseButtonDown(int btn) => false;
-
- public void Init() { }
}
}
diff --git a/src/Inspectors/GameObjects/ChildList.cs b/src/Inspectors/GameObjects/ChildList.cs
new file mode 100644
index 0000000..1d0d23a
--- /dev/null
+++ b/src/Inspectors/GameObjects/ChildList.cs
@@ -0,0 +1,219 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityExplorer.Helpers;
+using UnityExplorer.UI;
+using UnityExplorer.UI.Shared;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityExplorer.Input;
+
+namespace UnityExplorer.Inspectors.GameObjects
+{
+ public class ChildList
+ {
+ internal static ChildList Instance;
+
+ public ChildList()
+ {
+ Instance = this;
+ }
+
+ public static PageHandler s_childListPageHandler;
+ private static GameObject s_childListContent;
+
+ private static GameObject[] s_allChildren = new GameObject[0];
+ private static readonly List s_childrenShortlist = new List();
+ private static int s_lastChildCount;
+
+ private static readonly List s_childListTexts = new List();
+ private static readonly List s_childListToggles = new List();
+
+ internal void RefreshChildObjectList()
+ {
+ var go = GameObjectInspector.ActiveInstance.TargetGO;
+
+ s_allChildren = new GameObject[go.transform.childCount];
+ for (int i = 0; i < go.transform.childCount; i++)
+ {
+ var child = go.transform.GetChild(i);
+ s_allChildren[i] = child.gameObject;
+ }
+
+ var objects = s_allChildren;
+ s_childListPageHandler.ListCount = objects.Length;
+
+ int newCount = 0;
+
+ foreach (var itemIndex in s_childListPageHandler)
+ {
+ newCount++;
+
+ // normalized index starting from 0
+ var i = itemIndex - s_childListPageHandler.StartIndex;
+
+ if (itemIndex >= objects.Length)
+ {
+ if (i > s_lastChildCount || i >= s_childListTexts.Count)
+ break;
+
+ GameObject label = s_childListTexts[i].transform.parent.parent.gameObject;
+ if (label.activeSelf)
+ label.SetActive(false);
+ }
+ else
+ {
+ GameObject obj = objects[itemIndex];
+
+ if (!obj)
+ continue;
+
+ if (i >= s_childrenShortlist.Count)
+ {
+ s_childrenShortlist.Add(obj);
+ AddChildListButton();
+ }
+ else
+ {
+ s_childrenShortlist[i] = obj;
+ }
+
+ var text = s_childListTexts[i];
+
+ var name = obj.name;
+
+ if (obj.transform.childCount > 0)
+ name = $"[{obj.transform.childCount}] {name}";
+
+ text.text = name;
+ text.color = obj.activeSelf ? Color.green : Color.red;
+
+ var tog = s_childListToggles[i];
+ tog.isOn = obj.activeSelf;
+
+ var label = text.transform.parent.parent.gameObject;
+ if (!label.activeSelf)
+ {
+ label.SetActive(true);
+ }
+ }
+ }
+
+ s_lastChildCount = newCount;
+ }
+
+ internal static void OnChildListObjectClicked(int index)
+ {
+ if (GameObjectInspector.ActiveInstance == null)
+ return;
+
+ if (index >= s_childrenShortlist.Count || !s_childrenShortlist[index])
+ return;
+
+ GameObjectInspector.ActiveInstance.ChangeInspectorTarget(s_childrenShortlist[index]);
+ GameObjectInspector.ActiveInstance.Update();
+ }
+
+ internal static void OnChildListPageTurn()
+ {
+ if (Instance == null)
+ return;
+
+ Instance.RefreshChildObjectList();
+ }
+
+ internal static void OnToggleClicked(int index, bool newVal)
+ {
+ if (GameObjectInspector.ActiveInstance == null)
+ return;
+
+ if (index >= s_childrenShortlist.Count || !s_childrenShortlist[index])
+ return;
+
+ var obj = s_childrenShortlist[index];
+ obj.SetActive(newVal);
+ }
+
+ #region UI CONSTRUCTION
+
+ internal void ConstructChildList(GameObject parent)
+ {
+ var vertGroupObj = UIFactory.CreateVerticalGroup(parent, new Color(1, 1, 1, 0));
+ var vertGroup = vertGroupObj.GetComponent();
+ vertGroup.childForceExpandHeight = false;
+ vertGroup.childForceExpandWidth = false;
+ vertGroup.childControlWidth = true;
+ vertGroup.spacing = 5;
+ var vertLayout = vertGroupObj.AddComponent();
+ vertLayout.minWidth = 120;
+ vertLayout.flexibleWidth = 25000;
+ vertLayout.minHeight = 200;
+ vertLayout.flexibleHeight = 5000;
+
+ var childTitleObj = UIFactory.CreateLabel(vertGroupObj, TextAnchor.MiddleLeft);
+ var childTitleText = childTitleObj.GetComponent();
+ childTitleText.text = "Children";
+ childTitleText.color = Color.grey;
+ childTitleText.fontSize = 14;
+ var childTitleLayout = childTitleObj.AddComponent();
+ childTitleLayout.minHeight = 30;
+
+ var childrenScrollObj = UIFactory.CreateScrollView(vertGroupObj, out s_childListContent, out SliderScrollbar scroller, new Color(0.07f, 0.07f, 0.07f));
+ var contentLayout = childrenScrollObj.GetComponent();
+ contentLayout.minHeight = 50;
+
+ s_childListPageHandler = new PageHandler(scroller);
+ s_childListPageHandler.ConstructUI(vertGroupObj);
+ s_childListPageHandler.OnPageChanged += OnChildListPageTurn;
+ }
+
+ internal void AddChildListButton()
+ {
+ int thisIndex = s_childListTexts.Count;
+
+ GameObject btnGroupObj = UIFactory.CreateHorizontalGroup(s_childListContent, new Color(0.07f, 0.07f, 0.07f));
+ HorizontalLayoutGroup btnGroup = btnGroupObj.GetComponent();
+ btnGroup.childForceExpandWidth = true;
+ btnGroup.childControlWidth = true;
+ btnGroup.childForceExpandHeight = false;
+ btnGroup.childControlHeight = true;
+ LayoutElement btnLayout = btnGroupObj.AddComponent();
+ btnLayout.flexibleWidth = 320;
+ btnLayout.minHeight = 25;
+ btnLayout.flexibleHeight = 0;
+ btnGroupObj.AddComponent();
+
+ var toggleObj = UIFactory.CreateToggle(btnGroupObj, out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f));
+ var toggleLayout = toggleObj.AddComponent();
+ toggleLayout.minHeight = 25;
+ toggleLayout.minWidth = 25;
+ toggleText.text = "";
+ toggle.isOn = false;
+ s_childListToggles.Add(toggle);
+ toggle.onValueChanged.AddListener((bool val) => { OnToggleClicked(thisIndex, val); });
+
+ GameObject mainButtonObj = UIFactory.CreateButton(btnGroupObj);
+ LayoutElement mainBtnLayout = mainButtonObj.AddComponent();
+ mainBtnLayout.minHeight = 25;
+ mainBtnLayout.flexibleHeight = 0;
+ mainBtnLayout.minWidth = 25;
+ mainBtnLayout.flexibleWidth = 999;
+ Button mainBtn = mainButtonObj.GetComponent();
+ ColorBlock mainColors = mainBtn.colors;
+ mainColors.normalColor = new Color(0.07f, 0.07f, 0.07f);
+ mainColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1);
+ mainBtn.colors = mainColors;
+ mainBtn.onClick.AddListener(() => { OnChildListObjectClicked(thisIndex); });
+
+ Text mainText = mainButtonObj.GetComponentInChildren();
+ mainText.alignment = TextAnchor.MiddleLeft;
+ mainText.horizontalOverflow = HorizontalWrapMode.Overflow;
+ mainText.resizeTextForBestFit = true;
+ mainText.resizeTextMaxSize = 14;
+ mainText.resizeTextMinSize = 10;
+ s_childListTexts.Add(mainText);
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Inspectors/GameObjects/ComponentList.cs b/src/Inspectors/GameObjects/ComponentList.cs
new file mode 100644
index 0000000..555c5ae
--- /dev/null
+++ b/src/Inspectors/GameObjects/ComponentList.cs
@@ -0,0 +1,228 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityExplorer.Helpers;
+using UnityExplorer.UI;
+using UnityExplorer.UI.Shared;
+using UnityExplorer.Unstrip;
+//using TMPro;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityExplorer.Input;
+
+namespace UnityExplorer.Inspectors.GameObjects
+{
+ public class ComponentList
+ {
+ internal static ComponentList Instance;
+
+ public ComponentList()
+ {
+ Instance = this;
+ }
+
+ public static PageHandler s_compListPageHandler;
+ private static Component[] s_allComps = new Component[0];
+ private static readonly List s_compShortlist = new List();
+ private static GameObject s_compListContent;
+ private static readonly List s_compListTexts = new List();
+ private static int s_lastCompCount;
+ public static readonly List s_compToggles = new List();
+
+ internal void RefreshComponentList()
+ {
+ var go = GameObjectInspector.ActiveInstance.TargetGO;
+
+ s_allComps = go.GetComponents().ToArray();
+
+ var components = s_allComps;
+ s_compListPageHandler.ListCount = components.Length;
+
+ //int startIndex = m_sceneListPageHandler.StartIndex;
+
+ int newCount = 0;
+
+ foreach (var itemIndex in s_compListPageHandler)
+ {
+ newCount++;
+
+ // normalized index starting from 0
+ var i = itemIndex - s_compListPageHandler.StartIndex;
+
+ if (itemIndex >= components.Length)
+ {
+ if (i > s_lastCompCount || i >= s_compListTexts.Count)
+ break;
+
+ GameObject label = s_compListTexts[i].transform.parent.parent.gameObject;
+ if (label.activeSelf)
+ label.SetActive(false);
+ }
+ else
+ {
+ Component comp = components[itemIndex];
+
+ if (!comp)
+ continue;
+
+ if (i >= s_compShortlist.Count)
+ {
+ s_compShortlist.Add(comp);
+ AddCompListButton();
+ }
+ else
+ {
+ s_compShortlist[i] = comp;
+ }
+
+ var text = s_compListTexts[i];
+
+ text.text = UISyntaxHighlight.ParseFullSyntax(ReflectionHelpers.GetActualType(comp), true);
+
+ var toggle = s_compToggles[i];
+ if (comp is Behaviour behaviour)
+ {
+ if (!toggle.gameObject.activeSelf)
+ toggle.gameObject.SetActive(true);
+
+ toggle.isOn = behaviour.enabled;
+ }
+ else
+ {
+ if (toggle.gameObject.activeSelf)
+ toggle.gameObject.SetActive(false);
+ }
+
+ var label = text.transform.parent.parent.gameObject;
+ if (!label.activeSelf)
+ {
+ label.SetActive(true);
+ }
+ }
+ }
+
+ s_lastCompCount = newCount;
+ }
+
+ internal static void OnCompToggleClicked(int index, bool value)
+ {
+ var comp = s_compShortlist[index];
+
+ (comp as Behaviour).enabled = value;
+ }
+
+ internal static void OnCompListObjectClicked(int index)
+ {
+ if (index >= s_compShortlist.Count || !s_compShortlist[index])
+ {
+ return;
+ }
+
+ InspectorManager.Instance.Inspect(s_compShortlist[index]);
+ }
+
+ internal static void OnCompListPageTurn()
+ {
+ if (Instance == null)
+ return;
+
+ Instance.RefreshComponentList();
+ }
+
+
+ #region UI CONSTRUCTION
+
+ internal void ConstructCompList(GameObject parent)
+ {
+ var vertGroupObj = UIFactory.CreateVerticalGroup(parent, new Color(1, 1, 1, 0));
+ var vertGroup = vertGroupObj.GetComponent();
+ vertGroup.childForceExpandHeight = false;
+ vertGroup.childForceExpandWidth = false;
+ vertGroup.childControlWidth = true;
+ vertGroup.spacing = 5;
+ var vertLayout = vertGroupObj.AddComponent();
+ vertLayout.minWidth = 120;
+ vertLayout.flexibleWidth = 25000;
+ vertLayout.minHeight = 200;
+ vertLayout.flexibleHeight = 5000;
+
+ var compTitleObj = UIFactory.CreateLabel(vertGroupObj, TextAnchor.MiddleLeft);
+ var compTitleText = compTitleObj.GetComponent();
+ compTitleText.text = "Components";
+ compTitleText.color = Color.grey;
+ compTitleText.fontSize = 14;
+ var childTitleLayout = compTitleObj.AddComponent();
+ childTitleLayout.minHeight = 30;
+
+ var compScrollObj = UIFactory.CreateScrollView(vertGroupObj, out s_compListContent, out SliderScrollbar scroller, new Color(0.07f, 0.07f, 0.07f));
+ var contentLayout = compScrollObj.AddComponent();
+ contentLayout.minHeight = 50;
+
+ s_compListPageHandler = new PageHandler(scroller);
+ s_compListPageHandler.ConstructUI(vertGroupObj);
+ s_compListPageHandler.OnPageChanged += OnCompListPageTurn;
+ }
+
+ internal void AddCompListButton()
+ {
+ int thisIndex = s_compListTexts.Count;
+
+ GameObject btnGroupObj = UIFactory.CreateHorizontalGroup(s_compListContent, new Color(0.07f, 0.07f, 0.07f));
+ HorizontalLayoutGroup btnGroup = btnGroupObj.GetComponent();
+ btnGroup.childForceExpandWidth = true;
+ btnGroup.childControlWidth = true;
+ btnGroup.childForceExpandHeight = false;
+ btnGroup.childControlHeight = true;
+ btnGroup.childAlignment = TextAnchor.MiddleLeft;
+ LayoutElement btnLayout = btnGroupObj.AddComponent();
+ btnLayout.minWidth = 25;
+ btnLayout.flexibleWidth = 999;
+ btnLayout.minHeight = 25;
+ btnLayout.flexibleHeight = 0;
+ btnGroupObj.AddComponent();
+
+ // Behaviour enabled toggle
+
+ var toggleObj = UIFactory.CreateToggle(btnGroupObj, out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f));
+ var toggleLayout = toggleObj.AddComponent();
+ toggleLayout.minHeight = 25;
+ toggleLayout.minWidth = 25;
+ toggleText.text = "";
+ toggle.isOn = false;
+ s_compToggles.Add(toggle);
+ toggle.onValueChanged.AddListener((bool val) => { OnCompToggleClicked(thisIndex, val); });
+
+ // Main component button
+
+ GameObject mainButtonObj = UIFactory.CreateButton(btnGroupObj);
+ LayoutElement mainBtnLayout = mainButtonObj.AddComponent();
+ mainBtnLayout.minHeight = 25;
+ mainBtnLayout.flexibleHeight = 0;
+ mainBtnLayout.minWidth = 25;
+ mainBtnLayout.flexibleWidth = 999;
+ Button mainBtn = mainButtonObj.GetComponent();
+ ColorBlock mainColors = mainBtn.colors;
+ mainColors.normalColor = new Color(0.07f, 0.07f, 0.07f);
+ mainColors.highlightedColor = new Color(0.2f, 0.2f, 0.2f, 1);
+ mainBtn.colors = mainColors;
+ mainBtn.onClick.AddListener(() => { OnCompListObjectClicked(thisIndex); });
+
+ // Component button text
+
+ Text mainText = mainButtonObj.GetComponentInChildren();
+ mainText.alignment = TextAnchor.MiddleLeft;
+ mainText.horizontalOverflow = HorizontalWrapMode.Overflow;
+ //mainText.color = SyntaxColors.Class_Instance.ToColor();
+ mainText.resizeTextForBestFit = true;
+ mainText.resizeTextMaxSize = 14;
+ mainText.resizeTextMinSize = 8;
+
+ s_compListTexts.Add(mainText);
+
+ // TODO remove component button
+ }
+
+
+ #endregion
+ }
+}
diff --git a/src/Inspectors/GameObjects/GameObjectControls.cs b/src/Inspectors/GameObjects/GameObjectControls.cs
new file mode 100644
index 0000000..de2ba4e
--- /dev/null
+++ b/src/Inspectors/GameObjects/GameObjectControls.cs
@@ -0,0 +1,630 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityExplorer.Helpers;
+using UnityExplorer.UI;
+//using TMPro;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityExplorer.Input;
+using UnityExplorer.Unstrip;
+
+namespace UnityExplorer.Inspectors.GameObjects
+{
+ public class GameObjectControls
+ {
+ internal static GameObjectControls Instance;
+
+ public GameObjectControls()
+ {
+ Instance = this;
+ }
+
+ private static InputField s_setParentInput;
+
+ private static ControlEditor s_positionControl;
+ private static ControlEditor s_localPosControl;
+ private static ControlEditor s_rotationControl;
+ private static ControlEditor s_scaleControl;
+
+ // Transform Vector editors
+
+ internal struct ControlEditor
+ {
+ public InputField fullValue;
+ public Slider[] sliders;
+ public InputField[] inputs;
+ public Text[] values;
+ }
+
+ internal static bool s_sliderChangedWanted;
+ private static Slider s_currentSlider;
+ private static ControlType s_currentSliderType;
+ private static VectorValue s_currentSliderValueType;
+ private static float s_currentSliderValue;
+
+ internal enum ControlType
+ {
+ position,
+ localPosition,
+ eulerAngles,
+ localScale
+ }
+
+ internal enum VectorValue
+ {
+ x, y, z
+ };
+
+ internal void RefreshControls()
+ {
+ var go = GameObjectInspector.ActiveInstance.TargetGO;
+
+ s_positionControl.fullValue.text = go.transform.position.ToStringLong();
+ s_positionControl.values[0].text = go.transform.position.x.ToString("F3");
+ s_positionControl.values[1].text = go.transform.position.y.ToString("F3");
+ s_positionControl.values[2].text = go.transform.position.z.ToString("F3");
+
+ s_localPosControl.fullValue.text = go.transform.localPosition.ToStringLong();
+ s_localPosControl.values[0].text = go.transform.localPosition.x.ToString("F3");
+ s_localPosControl.values[1].text = go.transform.localPosition.y.ToString("F3");
+ s_localPosControl.values[2].text = go.transform.localPosition.z.ToString("F3");
+
+ s_rotationControl.fullValue.text = go.transform.eulerAngles.ToStringLong();
+ s_rotationControl.values[0].text = go.transform.eulerAngles.x.ToString("F3");
+ s_rotationControl.values[1].text = go.transform.eulerAngles.y.ToString("F3");
+ s_rotationControl.values[2].text = go.transform.eulerAngles.z.ToString("F3");
+
+ s_scaleControl.fullValue.text = go.transform.localScale.ToStringLong();
+ s_scaleControl.values[0].text = go.transform.localScale.x.ToString("F3");
+ s_scaleControl.values[1].text = go.transform.localScale.y.ToString("F3");
+ s_scaleControl.values[2].text = go.transform.localScale.z.ToString("F3");
+
+ }
+
+ internal static void OnSetParentClicked()
+ {
+ var go = GameObjectInspector.ActiveInstance.TargetGO;
+
+ if (!go)
+ return;
+
+ var input = s_setParentInput.text;
+
+ if (string.IsNullOrEmpty(input))
+ {
+ go.transform.parent = null;
+ }
+ else
+ {
+ if (GameObject.Find(input) is GameObject newParent)
+ {
+ go.transform.parent = newParent.transform;
+ }
+ else
+ {
+ ExplorerCore.Log($"Could not find any GameObject from name or path '{input}'! Note: The target must be enabled.");
+ }
+ }
+ }
+
+ internal static void OnSliderControlChanged(float value, Slider slider, ControlType controlType, VectorValue vectorValue)
+ {
+ if (value == 0)
+ s_sliderChangedWanted = false;
+ else
+ {
+ if (!s_sliderChangedWanted)
+ {
+ s_sliderChangedWanted = true;
+ s_currentSlider = slider;
+ s_currentSliderType = controlType;
+ s_currentSliderValueType = vectorValue;
+ }
+
+ s_currentSliderValue = value;
+ }
+ }
+
+ internal static void UpdateSliderControl()
+ {
+ if (!InputManager.GetMouseButton(0))
+ {
+ s_sliderChangedWanted = false;
+ s_currentSlider.value = 0;
+
+ return;
+ }
+
+ if (GameObjectInspector.ActiveInstance == null) return;
+
+ var transform = GameObjectInspector.ActiveInstance.TargetGO.transform;
+
+ // get the current vector for the control type
+ Vector3 vector = Vector2.zero;
+ switch (s_currentSliderType)
+ {
+ case ControlType.position:
+ vector = transform.position; break;
+ case ControlType.localPosition:
+ vector = transform.localPosition; break;
+ case ControlType.eulerAngles:
+ vector = transform.eulerAngles; break;
+ case ControlType.localScale:
+ vector = transform.localScale; break;
+ }
+
+ // apply vector value change
+ switch (s_currentSliderValueType)
+ {
+ case VectorValue.x:
+ vector.x += s_currentSliderValue; break;
+ case VectorValue.y:
+ vector.y += s_currentSliderValue; break;
+ case VectorValue.z:
+ vector.z += s_currentSliderValue; break;
+ }
+
+ // set vector to transform member
+ switch (s_currentSliderType)
+ {
+ case ControlType.position:
+ transform.position = vector; break;
+ case ControlType.localPosition:
+ transform.localPosition = vector; break;
+ case ControlType.eulerAngles:
+ transform.eulerAngles = vector; break;
+ case ControlType.localScale:
+ transform.localScale = vector; break;
+ }
+ }
+
+ internal static void OnVectorControlInputApplied(ControlType controlType, VectorValue vectorValue)
+ {
+ if (!(InspectorManager.Instance.m_activeInspector is GameObjectInspector instance)) return;
+
+ // get relevant input for controltype + value
+
+ InputField[] inputs = null;
+ switch (controlType)
+ {
+ case ControlType.position:
+ inputs = s_positionControl.inputs; break;
+ case ControlType.localPosition:
+ inputs = s_localPosControl.inputs; break;
+ case ControlType.eulerAngles:
+ inputs = s_rotationControl.inputs; break;
+ case ControlType.localScale:
+ inputs = s_scaleControl.inputs; break;
+ }
+ InputField input = inputs[(int)vectorValue];
+
+ float val = float.Parse(input.text);
+
+ // apply transform value
+
+ Vector3 vector = Vector3.zero;
+ var transform = instance.TargetGO.transform;
+ switch (controlType)
+ {
+ case ControlType.position:
+ vector = transform.position; break;
+ case ControlType.localPosition:
+ vector = transform.localPosition; break;
+ case ControlType.eulerAngles:
+ vector = transform.eulerAngles; break;
+ case ControlType.localScale:
+ vector = transform.localScale; break;
+ }
+
+ switch (vectorValue)
+ {
+ case VectorValue.x:
+ vector.x = val; break;
+ case VectorValue.y:
+ vector.y = val; break;
+ case VectorValue.z:
+ vector.z = val; break;
+ }
+
+ // set back to transform
+ switch (controlType)
+ {
+ case ControlType.position:
+ transform.position = vector; break;
+ case ControlType.localPosition:
+ transform.localPosition = vector; break;
+ case ControlType.eulerAngles:
+ transform.eulerAngles = vector; break;
+ case ControlType.localScale:
+ transform.localScale = vector; break;
+ }
+ }
+
+ #region UI CONSTRUCTION
+
+ internal void ConstructControls(GameObject parent)
+ {
+ var controlsObj = UIFactory.CreateVerticalGroup(parent, new Color(0.07f, 0.07f, 0.07f));
+ var controlsGroup = controlsObj.GetComponent();
+ controlsGroup.childForceExpandWidth = false;
+ controlsGroup.childControlWidth = true;
+ controlsGroup.childForceExpandHeight = false;
+ controlsGroup.spacing = 5;
+ controlsGroup.padding.top = 4;
+ controlsGroup.padding.left = 4;
+ controlsGroup.padding.right = 4;
+ controlsGroup.padding.bottom = 4;
+
+ // ~~~~~~ Top row ~~~~~~
+
+ var topRow = UIFactory.CreateHorizontalGroup(controlsObj, new Color(1, 1, 1, 0));
+ var topRowGroup = topRow.GetComponent();
+ topRowGroup.childForceExpandWidth = false;
+ topRowGroup.childControlWidth = true;
+ topRowGroup.childForceExpandHeight = false;
+ topRowGroup.childControlHeight = true;
+ topRowGroup.spacing = 5;
+
+ var hideButtonObj = UIFactory.CreateButton(topRow);
+ var hideButton = hideButtonObj.GetComponent();
+ var hideColors = hideButton.colors;
+ hideColors.normalColor = new Color(0.16f, 0.16f, 0.16f);
+ hideButton.colors = hideColors;
+ var hideText = hideButtonObj.GetComponentInChildren();
+ hideText.text = "Show";
+ hideText.fontSize = 14;
+ var hideButtonLayout = hideButtonObj.AddComponent();
+ hideButtonLayout.minWidth = 40;
+ hideButtonLayout.flexibleWidth = 0;
+ hideButtonLayout.minHeight = 25;
+ hideButtonLayout.flexibleHeight = 0;
+
+ var topTitle = UIFactory.CreateLabel(topRow, TextAnchor.MiddleLeft);
+ var topText = topTitle.GetComponent();
+ topText.text = "Controls";
+ var titleLayout = topTitle.AddComponent();
+ titleLayout.minWidth = 100;
+ titleLayout.flexibleWidth = 9500;
+ titleLayout.minHeight = 25;
+
+ //// ~~~~~~~~ Content ~~~~~~~~ //
+
+ var contentObj = UIFactory.CreateVerticalGroup(controlsObj, new Color(1, 1, 1, 0));
+ var contentGroup = contentObj.GetComponent();
+ contentGroup.childForceExpandHeight = false;
+ contentGroup.childControlHeight = true;
+ contentGroup.spacing = 5;
+ contentGroup.childForceExpandWidth = true;
+ contentGroup.childControlWidth = true;
+
+ // ~~ add hide button callback now that we have scroll reference ~~
+ hideButton.onClick.AddListener(OnHideClicked);
+ void OnHideClicked()
+ {
+ if (hideText.text == "Show")
+ {
+ hideText.text = "Hide";
+ contentObj.SetActive(true);
+ }
+ else
+ {
+ hideText.text = "Show";
+ contentObj.SetActive(false);
+ }
+ }
+
+ // transform controls
+ ConstructVector3Editor(contentObj, "Position", ControlType.position, out s_positionControl);
+ ConstructVector3Editor(contentObj, "Local Position", ControlType.localPosition, out s_localPosControl);
+ ConstructVector3Editor(contentObj, "Rotation", ControlType.eulerAngles, out s_rotationControl);
+ ConstructVector3Editor(contentObj, "Scale", ControlType.localScale, out s_scaleControl);
+
+ // set parent
+ ConstructSetParent(contentObj);
+
+ // bottom row buttons
+ ConstructBottomButtons(contentObj);
+
+ // set controls content inactive now that content is made (otherwise TMP font size goes way too big?)
+ contentObj.SetActive(false);
+ }
+
+ internal void ConstructSetParent(GameObject contentObj)
+ {
+ var setParentGroupObj = UIFactory.CreateHorizontalGroup(contentObj, new Color(1, 1, 1, 0));
+ var setParentGroup = setParentGroupObj.GetComponent();
+ setParentGroup.childForceExpandHeight = false;
+ setParentGroup.childControlHeight = true;
+ setParentGroup.childForceExpandWidth = false;
+ setParentGroup.childControlWidth = true;
+ setParentGroup.spacing = 5;
+ var setParentLayout = setParentGroupObj.AddComponent();
+ setParentLayout.minHeight = 25;
+ setParentLayout.flexibleHeight = 0;
+
+ var setParentLabelObj = UIFactory.CreateLabel(setParentGroupObj, TextAnchor.MiddleLeft);
+ var setParentLabel = setParentLabelObj.GetComponent();
+ setParentLabel.text = "Set Parent:";
+ setParentLabel.color = Color.grey;
+ setParentLabel.fontSize = 14;
+ var setParentLabelLayout = setParentLabelObj.AddComponent();
+ setParentLabelLayout.minWidth = 110;
+ setParentLabelLayout.minHeight = 25;
+ setParentLabelLayout.flexibleWidth = 0;
+
+ var setParentInputObj = UIFactory.CreateInputField(setParentGroupObj);
+ s_setParentInput = setParentInputObj.GetComponent();
+ var placeholderInput = s_setParentInput.placeholder.GetComponent();
+ placeholderInput.text = "Enter a GameObject name or path...";
+ var setParentInputLayout = setParentInputObj.AddComponent();
+ setParentInputLayout.minHeight = 25;
+ setParentInputLayout.preferredWidth = 400;
+ setParentInputLayout.flexibleWidth = 9999;
+
+ var applyButtonObj = UIFactory.CreateButton(setParentGroupObj);
+ var applyButton = applyButtonObj.GetComponent();
+ applyButton.onClick.AddListener(OnSetParentClicked);
+ var applyText = applyButtonObj.GetComponentInChildren();
+ applyText.text = "Apply";
+ var applyLayout = applyButtonObj.AddComponent();
+ applyLayout.minWidth = 55;
+ applyLayout.flexibleWidth = 0;
+ applyLayout.minHeight = 25;
+ applyLayout.flexibleHeight = 0;
+ }
+
+ internal void ConstructVector3Editor(GameObject parent, string title, ControlType type, out ControlEditor editor)
+ {
+ editor = new ControlEditor();
+
+ var topBarObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
+ var topGroup = topBarObj.GetComponent();
+ topGroup.childForceExpandWidth = false;
+ topGroup.childControlWidth = true;
+ topGroup.childForceExpandHeight = false;
+ topGroup.childControlHeight = true;
+ topGroup.spacing = 5;
+ var topLayout = topBarObj.AddComponent();
+ topLayout.minHeight = 25;
+ topLayout.flexibleHeight = 0;
+
+ var titleObj = UIFactory.CreateLabel(topBarObj, TextAnchor.MiddleLeft);
+ var titleText = titleObj.GetComponent();
+ titleText.text = title;
+ titleText.color = Color.grey;
+ titleText.fontSize = 14;
+ var titleLayout = titleObj.AddComponent();
+ titleLayout.minWidth = 110;
+ titleLayout.flexibleWidth = 0;
+ titleLayout.minHeight = 25;
+
+ // expand button
+ var expandButtonObj = UIFactory.CreateButton(topBarObj);
+ var expandButton = expandButtonObj.GetComponent();
+ var expandText = expandButtonObj.GetComponentInChildren();
+ expandText.text = "▼";
+ expandText.fontSize = 12;
+ var btnLayout = expandButtonObj.AddComponent();
+ btnLayout.minWidth = 35;
+ btnLayout.flexibleWidth = 0;
+ btnLayout.minHeight = 25;
+ btnLayout.flexibleHeight = 0;
+
+ // readonly value input
+
+ var valueInputObj = UIFactory.CreateInputField(topBarObj);
+ var valueInput = valueInputObj.GetComponent();
+ valueInput.readOnly = true;
+ //valueInput.richText = true;
+ //valueInput.isRichTextEditingAllowed = true;
+ var valueInputLayout = valueInputObj.AddComponent();
+ valueInputLayout.minHeight = 25;
+ valueInputLayout.flexibleHeight = 0;
+ valueInputLayout.preferredWidth = 400;
+ valueInputLayout.flexibleWidth = 9999;
+
+ editor.fullValue = valueInput;
+
+ editor.sliders = new Slider[3];
+ editor.inputs = new InputField[3];
+ editor.values = new Text[3];
+
+ var xRow = ConstructEditorRow(parent, editor, type, VectorValue.x);
+ xRow.SetActive(false);
+ var yRow = ConstructEditorRow(parent, editor, type, VectorValue.y);
+ yRow.SetActive(false);
+ var zRow = ConstructEditorRow(parent, editor, type, VectorValue.z);
+ zRow.SetActive(false);
+
+ // add expand callback now that we have group reference
+ expandButton.onClick.AddListener(ToggleExpand);
+ void ToggleExpand()
+ {
+ if (xRow.activeSelf)
+ {
+ xRow.SetActive(false);
+ yRow.SetActive(false);
+ zRow.SetActive(false);
+ expandText.text = "▼";
+ }
+ else
+ {
+ xRow.SetActive(true);
+ yRow.SetActive(true);
+ zRow.SetActive(true);
+ expandText.text = "▲";
+ }
+ }
+ }
+
+ internal GameObject ConstructEditorRow(GameObject parent, ControlEditor editor, ControlType type, VectorValue vectorValue)
+ {
+ var rowObject = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
+ var rowGroup = rowObject.GetComponent();
+ rowGroup.childForceExpandWidth = false;
+ rowGroup.childControlWidth = true;
+ rowGroup.childForceExpandHeight = false;
+ rowGroup.childControlHeight = true;
+ rowGroup.spacing = 5;
+ var rowLayout = rowObject.AddComponent();
+ rowLayout.minHeight = 25;
+ rowLayout.flexibleHeight = 0;
+ rowLayout.minWidth = 100;
+
+ // Value labels
+
+ var labelObj = UIFactory.CreateLabel(rowObject, TextAnchor.MiddleLeft);
+ var labelText = labelObj.GetComponent();
+ labelText.color = Color.cyan;
+ labelText.text = $"{vectorValue.ToString().ToUpper()}:";
+ labelText.fontSize = 14;
+ labelText.resizeTextMaxSize = 14;
+ labelText.resizeTextForBestFit = true;
+ var labelLayout = labelObj.AddComponent();
+ labelLayout.minHeight = 25;
+ labelLayout.flexibleHeight = 0;
+ labelLayout.minWidth = 25;
+ labelLayout.flexibleWidth = 0;
+
+ // actual value label
+ var valueLabelObj = UIFactory.CreateLabel(rowObject, TextAnchor.MiddleLeft);
+ var valueLabel = valueLabelObj.GetComponent();
+ editor.values[(int)vectorValue] = valueLabel;
+ var valueLabelLayout = valueLabelObj.AddComponent();
+ valueLabelLayout.minWidth = 85;
+ valueLabelLayout.flexibleWidth = 0;
+ valueLabelLayout.minHeight = 25;
+
+ // input field
+
+ var inputHolder = UIFactory.CreateVerticalGroup(rowObject, new Color(1, 1, 1, 0));
+ var inputHolderGroup = inputHolder.GetComponent();
+ inputHolderGroup.childForceExpandHeight = false;
+ inputHolderGroup.childControlHeight = true;
+ inputHolderGroup.childForceExpandWidth = false;
+ inputHolderGroup.childControlWidth = true;
+
+ var inputObj = UIFactory.CreateInputField(inputHolder);
+ var input = inputObj.GetComponent();
+ input.characterValidation = InputField.CharacterValidation.Decimal;
+
+ var inputLayout = inputObj.AddComponent();
+ inputLayout.minHeight = 25;
+ inputLayout.flexibleHeight = 0;
+ inputLayout.minWidth = 90;
+ inputLayout.flexibleWidth = 50;
+
+ editor.inputs[(int)vectorValue] = input;
+
+ // apply button
+
+ var applyBtnObj = UIFactory.CreateButton(rowObject);
+ var applyBtn = applyBtnObj.GetComponent();
+ var applyText = applyBtnObj.GetComponentInChildren();
+ applyText.text = "Apply";
+ applyText.fontSize = 14;
+ var applyLayout = applyBtnObj.AddComponent();
+ applyLayout.minWidth = 60;
+ applyLayout.minHeight = 25;
+
+ applyBtn.onClick.AddListener(() => { OnVectorControlInputApplied(type, vectorValue); });
+
+ // Slider
+
+ var sliderObj = UIFactory.CreateSlider(rowObject);
+ sliderObj.transform.Find("Fill Area").gameObject.SetActive(false);
+ var sliderLayout = sliderObj.AddComponent();
+ sliderLayout.minHeight = 20;
+ sliderLayout.flexibleHeight = 0;
+ sliderLayout.minWidth = 200;
+ sliderLayout.flexibleWidth = 9000;
+ var slider = sliderObj.GetComponent();
+ var sliderColors = slider.colors;
+ sliderColors.normalColor = new Color(0.65f, 0.65f, 0.65f);
+ slider.colors = sliderColors;
+ slider.minValue = -2;
+ slider.maxValue = 2;
+ slider.value = 0;
+ slider.onValueChanged.AddListener((float val) => { OnSliderControlChanged(val, slider, type, vectorValue); });
+ editor.sliders[(int)vectorValue] = slider;
+
+ return rowObject;
+ }
+
+ internal void ConstructBottomButtons(GameObject contentObj)
+ {
+ var bottomRow = UIFactory.CreateHorizontalGroup(contentObj, new Color(1, 1, 1, 0));
+ var bottomGroup = bottomRow.GetComponent();
+ bottomGroup.childForceExpandWidth = true;
+ bottomGroup.childControlWidth = true;
+ bottomGroup.spacing = 4;
+ var bottomLayout = bottomRow.AddComponent();
+ bottomLayout.minHeight = 25;
+
+ var instantiateBtnObj = UIFactory.CreateButton(bottomRow, new Color(0.2f, 0.2f, 0.2f));
+ var instantiateBtn = instantiateBtnObj.GetComponent();
+
+ instantiateBtn.onClick.AddListener(InstantiateBtn);
+
+ var instantiateText = instantiateBtnObj.GetComponentInChildren();
+ instantiateText.text = "Instantiate";
+ instantiateText.fontSize = 14;
+ var instantiateLayout = instantiateBtnObj.AddComponent();
+ instantiateLayout.minWidth = 150;
+
+ void InstantiateBtn()
+ {
+ var go = GameObjectInspector.ActiveInstance.TargetGO;
+ if (!go)
+ return;
+
+ var clone = GameObject.Instantiate(go);
+ InspectorManager.Instance.Inspect(clone);
+ }
+
+ var dontDestroyBtnObj = UIFactory.CreateButton(bottomRow, new Color(0.2f, 0.2f, 0.2f));
+ var dontDestroyBtn = dontDestroyBtnObj.GetComponent();
+
+ dontDestroyBtn.onClick.AddListener(DontDestroyOnLoadBtn);
+
+ var dontDestroyText = dontDestroyBtnObj.GetComponentInChildren();
+ dontDestroyText.text = "Set DontDestroyOnLoad";
+ dontDestroyText.fontSize = 14;
+ var dontDestroyLayout = dontDestroyBtnObj.AddComponent();
+ dontDestroyLayout.flexibleWidth = 5000;
+
+ void DontDestroyOnLoadBtn()
+ {
+ var go = GameObjectInspector.ActiveInstance.TargetGO;
+ if (!go)
+ return;
+
+ GameObject.DontDestroyOnLoad(go);
+ }
+
+ var destroyBtnObj = UIFactory.CreateButton(bottomRow, new Color(0.2f, 0.2f, 0.2f));
+ var destroyBtn = destroyBtnObj.GetComponent();
+
+ destroyBtn.onClick.AddListener(DestroyBtn);
+
+ var destroyText = destroyBtnObj.GetComponentInChildren();
+ destroyText.text = "Destroy";
+ destroyText.fontSize = 14;
+ destroyText.color = Color.red;
+ var destroyLayout = destroyBtnObj.AddComponent();
+ destroyLayout.minWidth = 150;
+
+ void DestroyBtn()
+ {
+ var go = GameObjectInspector.ActiveInstance.TargetGO;
+ if (!go)
+ return;
+
+ GameObject.Destroy(go);
+ }
+ }
+
+#endregion
+ }
+}
diff --git a/src/Inspectors/GameObjects/GameObjectInspector.cs b/src/Inspectors/GameObjects/GameObjectInspector.cs
new file mode 100644
index 0000000..9658cb0
--- /dev/null
+++ b/src/Inspectors/GameObjects/GameObjectInspector.cs
@@ -0,0 +1,444 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityExplorer.Helpers;
+using UnityExplorer.UI;
+using UnityExplorer.Unstrip;
+//using TMPro;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityExplorer.Inspectors.GameObjects;
+
+namespace UnityExplorer.Inspectors
+{
+ public class GameObjectInspector : InspectorBase
+ {
+ public override string TabLabel => $" [G] {TargetGO?.name}";
+
+ public static GameObjectInspector ActiveInstance { get; private set; }
+
+ public GameObject TargetGO;
+
+ // sub modules
+ private static ChildList s_childList;
+ private static ComponentList s_compList;
+ private static GameObjectControls s_controls;
+
+ // static UI elements (only constructed once)
+
+ private static bool m_UIConstructed;
+
+ private static GameObject s_content;
+ public override GameObject Content
+ {
+ get => s_content;
+ set => s_content = value;
+ }
+
+ private static string m_lastName;
+ public static InputField m_nameInput;
+
+ private static string m_lastPath;
+ public static InputField m_pathInput;
+ private static RectTransform m_pathInputRect;
+ private static GameObject m_pathGroupObj;
+ private static Text m_hiddenPathText;
+ private static RectTransform m_hiddenPathRect;
+
+ private static Toggle m_enabledToggle;
+ private static Text m_enabledText;
+ private static bool? m_lastEnabledState;
+
+ private static Dropdown m_layerDropdown;
+ private static int m_lastLayer = -1;
+
+ private static Text m_sceneText;
+ private static string m_lastScene;
+
+ public GameObjectInspector(GameObject target) : base(target)
+ {
+ ActiveInstance = this;
+
+ TargetGO = target;
+
+ if (!TargetGO)
+ {
+ ExplorerCore.LogWarning("Target GameObject is null!");
+ return;
+ }
+
+ // one UI is used for all gameobject inspectors. no point recreating it.
+ if (!m_UIConstructed)
+ {
+ m_UIConstructed = true;
+
+ s_childList = new ChildList();
+ s_compList = new ComponentList();
+ s_controls = new GameObjectControls();
+
+ ConstructUI();
+ }
+ }
+
+ public override void SetActive()
+ {
+ base.SetActive();
+ ActiveInstance = this;
+ }
+
+ public override void SetInactive()
+ {
+ base.SetInactive();
+ ActiveInstance = null;
+ }
+
+ internal void ChangeInspectorTarget(GameObject newTarget)
+ {
+ if (!newTarget)
+ return;
+
+ this.Target = this.TargetGO = newTarget;
+ }
+
+ // Update
+
+ public override void Update()
+ {
+ base.Update();
+
+ if (m_pendingDestroy || !this.IsActive)
+ return;
+
+ RefreshTopInfo();
+
+ s_childList.RefreshChildObjectList();
+
+ s_compList.RefreshComponentList();
+
+ s_controls.RefreshControls();
+
+ if (GameObjectControls.s_sliderChangedWanted)
+ GameObjectControls.UpdateSliderControl();
+ }
+
+ private void RefreshTopInfo()
+ {
+ if (m_lastName != TargetGO.name)
+ {
+ m_lastName = TargetGO.name;
+ m_nameInput.text = m_lastName;
+ }
+
+ if (TargetGO.transform.parent)
+ {
+ if (!m_pathGroupObj.activeSelf)
+ m_pathGroupObj.SetActive(true);
+
+ var path = TargetGO.transform.GetTransformPath(true);
+ if (m_lastPath != path)
+ {
+ m_lastPath = path;
+
+ m_pathInput.text = path;
+ m_hiddenPathText.text = path;
+
+ LayoutRebuilder.ForceRebuildLayoutImmediate(m_pathInputRect);
+ LayoutRebuilder.ForceRebuildLayoutImmediate(m_hiddenPathRect);
+ }
+ }
+ else if (m_pathGroupObj.activeSelf)
+ m_pathGroupObj.SetActive(false);
+
+ if (m_lastEnabledState != TargetGO.activeSelf)
+ {
+ m_lastEnabledState = TargetGO.activeSelf;
+
+ m_enabledToggle.isOn = TargetGO.activeSelf;
+ m_enabledText.text = TargetGO.activeSelf ? "Enabled" : "Disabled";
+ m_enabledText.color = TargetGO.activeSelf ? Color.green : Color.red;
+ }
+
+ if (m_lastLayer != TargetGO.layer)
+ {
+ m_lastLayer = TargetGO.layer;
+ m_layerDropdown.value = TargetGO.layer;
+ }
+
+ if (m_lastScene != TargetGO.scene.name)
+ {
+ m_lastScene = TargetGO.scene.name;
+
+ if (!string.IsNullOrEmpty(TargetGO.scene.name))
+ m_sceneText.text = m_lastScene;
+ else
+ m_sceneText.text = "None (Asset/Resource)";
+ }
+ }
+
+ // UI Callbacks
+
+ private static void OnApplyNameClicked()
+ {
+ if (ActiveInstance == null)
+ return;
+
+ ActiveInstance.TargetGO.name = m_nameInput.text;
+ }
+
+ private static void OnEnableToggled(bool enabled)
+ {
+ if (ActiveInstance == null)
+ return;
+
+ ActiveInstance.TargetGO.SetActive(enabled);
+ }
+
+ private static void OnLayerSelected(int layer)
+ {
+ if (ActiveInstance == null)
+ return;
+
+ ActiveInstance.TargetGO.layer = layer;
+ }
+
+ internal static void OnBackButtonClicked()
+ {
+ if (ActiveInstance == null)
+ return;
+
+ ActiveInstance.ChangeInspectorTarget(ActiveInstance.TargetGO.transform.parent.gameObject);
+ }
+
+ #region UI CONSTRUCTION
+
+ private void ConstructUI()
+ {
+ var parent = InspectorManager.Instance.m_inspectorContent;
+
+ s_content = UIFactory.CreateScrollView(parent, out GameObject scrollContent, out _, new Color(0.1f, 0.1f, 0.1f));
+
+ var scrollGroup = scrollContent.GetComponent();
+ scrollGroup.childForceExpandHeight = true;
+ scrollGroup.childControlHeight = true;
+ scrollGroup.spacing = 5;
+
+ ConstructTopArea(scrollContent);
+
+ s_controls.ConstructControls(scrollContent);
+
+ var midGroupObj = ConstructMidGroup(scrollContent);
+
+ s_childList.ConstructChildList(midGroupObj);
+ s_compList.ConstructCompList(midGroupObj);
+ }
+
+ private void ConstructTopArea(GameObject scrollContent)
+ {
+ // path row
+
+ m_pathGroupObj = UIFactory.CreateHorizontalGroup(scrollContent, new Color(0.1f, 0.1f, 0.1f));
+ var pathGroup = m_pathGroupObj.GetComponent();
+ pathGroup.childForceExpandHeight = false;
+ pathGroup.childForceExpandWidth = false;
+ pathGroup.childControlHeight = false;
+ pathGroup.childControlWidth = true;
+ pathGroup.spacing = 5;
+ var pathRect = m_pathGroupObj.GetComponent();
+ pathRect.sizeDelta = new Vector2(pathRect.sizeDelta.x, 20);
+ var pathLayout = m_pathGroupObj.AddComponent();
+ pathLayout.minHeight = 20;
+ pathLayout.flexibleHeight = 75;
+
+ var backButtonObj = UIFactory.CreateButton(m_pathGroupObj);
+ var backButton = backButtonObj.GetComponent();
+
+ backButton.onClick.AddListener(OnBackButtonClicked);
+
+ var backColors = backButton.colors;
+ backColors.normalColor = new Color(0.15f, 0.15f, 0.15f);
+ backButton.colors = backColors;
+ var backText = backButtonObj.GetComponentInChildren();
+ backText.text = "◄";
+ var backLayout = backButtonObj.AddComponent();
+ backLayout.minWidth = 55;
+ backLayout.flexibleWidth = 0;
+ backLayout.minHeight = 25;
+ backLayout.flexibleHeight = 0;
+
+ var pathHiddenTextObj = UIFactory.CreateLabel(m_pathGroupObj, TextAnchor.MiddleLeft);
+ m_hiddenPathText = pathHiddenTextObj.GetComponent();
+ m_hiddenPathText.color = Color.clear;
+ m_hiddenPathText.fontSize = 14;
+ //m_hiddenPathText.lineSpacing = 1.5f;
+ m_hiddenPathText.raycastTarget = false;
+ var hiddenFitter = pathHiddenTextObj.AddComponent();
+ hiddenFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
+ var hiddenLayout = pathHiddenTextObj.AddComponent();
+ hiddenLayout.minHeight = 25;
+ hiddenLayout.flexibleHeight = 125;
+ hiddenLayout.minWidth = 250;
+ hiddenLayout.flexibleWidth = 9000;
+ var hiddenGroup = pathHiddenTextObj.AddComponent();
+ hiddenGroup.childForceExpandWidth = true;
+ hiddenGroup.childControlWidth = true;
+ hiddenGroup.childForceExpandHeight = true;
+ hiddenGroup.childControlHeight = true;
+
+ var pathInputObj = UIFactory.CreateInputField(pathHiddenTextObj);
+ var pathInputRect = pathInputObj.GetComponent();
+ pathInputRect.sizeDelta = new Vector2(pathInputRect.sizeDelta.x, 25);
+ m_pathInput = pathInputObj.GetComponent();
+ m_pathInput.text = TargetGO.transform.GetTransformPath();
+ m_pathInput.readOnly = true;
+ m_pathInput.lineType = InputField.LineType.MultiLineNewline;
+ var pathInputLayout = pathInputObj.AddComponent();
+ pathInputLayout.minHeight = 25;
+ pathInputLayout.flexibleHeight = 75;
+ pathInputLayout.preferredWidth = 400;
+ pathInputLayout.flexibleWidth = 9999;
+ var textRect = m_pathInput.textComponent.GetComponent();
+ textRect.offsetMin = new Vector2(3, 3);
+ textRect.offsetMax = new Vector2(3, 3);
+ m_pathInput.textComponent.color = new Color(0.75f, 0.75f, 0.75f);
+ //m_pathInput.textComponent.lineSpacing = 1.5f;
+
+ m_pathInputRect = m_pathInput.GetComponent();
+ m_hiddenPathRect = m_hiddenPathText.GetComponent();
+
+ // name row
+
+ var nameRowObj = UIFactory.CreateHorizontalGroup(scrollContent, new Color(0.1f, 0.1f, 0.1f));
+ var nameGroup = nameRowObj.GetComponent();
+ nameGroup.childForceExpandHeight = false;
+ nameGroup.childForceExpandWidth = false;
+ nameGroup.childControlHeight = true;
+ nameGroup.childControlWidth = true;
+ nameGroup.spacing = 5;
+ var nameRect = nameRowObj.GetComponent();
+ nameRect.sizeDelta = new Vector2(nameRect.sizeDelta.x, 25);
+ var nameLayout = nameRowObj.AddComponent();
+ nameLayout.minHeight = 25;
+ nameLayout.flexibleHeight = 0;
+
+ var nameTextObj = UIFactory.CreateLabel(nameRowObj, TextAnchor.MiddleCenter);
+ var nameTextText = nameTextObj.GetComponent();
+ nameTextText.text = "Name:";
+ nameTextText.fontSize = 14;
+ nameTextText.color = Color.grey;
+ var nameTextLayout = nameTextObj.AddComponent();
+ nameTextLayout.minHeight = 25;
+ nameTextLayout.flexibleHeight = 0;
+ nameTextLayout.minWidth = 55;
+ nameTextLayout.flexibleWidth = 0;
+
+ var nameInputObj = UIFactory.CreateInputField(nameRowObj);
+ var nameInputRect = nameInputObj.GetComponent();
+ nameInputRect.sizeDelta = new Vector2(nameInputRect.sizeDelta.x, 25);
+ m_nameInput = nameInputObj.GetComponent();
+ m_nameInput.text = TargetGO.name;
+
+ var applyNameBtnObj = UIFactory.CreateButton(nameRowObj);
+ var applyNameBtn = applyNameBtnObj.GetComponent();
+
+ applyNameBtn.onClick.AddListener(OnApplyNameClicked);
+
+ var applyNameText = applyNameBtnObj.GetComponentInChildren();
+ applyNameText.text = "Apply";
+ applyNameText.fontSize = 14;
+ var applyNameLayout = applyNameBtnObj.AddComponent();
+ applyNameLayout.minWidth = 65;
+ applyNameLayout.minHeight = 25;
+ applyNameLayout.flexibleHeight = 0;
+ var applyNameRect = applyNameBtnObj.GetComponent();
+ applyNameRect.sizeDelta = new Vector2(applyNameRect.sizeDelta.x, 25);
+
+ var activeLabel = UIFactory.CreateLabel(nameRowObj, TextAnchor.MiddleCenter);
+ var activeLabelLayout = activeLabel.AddComponent();
+ activeLabelLayout.minWidth = 55;
+ activeLabelLayout.minHeight = 25;
+ var activeText = activeLabel.GetComponent();
+ activeText.text = "Active:";
+ activeText.color = Color.grey;
+ activeText.fontSize = 14;
+
+ var enabledToggleObj = UIFactory.CreateToggle(nameRowObj, out m_enabledToggle, out m_enabledText);
+ var toggleLayout = enabledToggleObj.AddComponent();
+ toggleLayout.minHeight = 25;
+ toggleLayout.minWidth = 100;
+ toggleLayout.flexibleWidth = 0;
+ m_enabledText.text = "Enabled";
+ m_enabledText.color = Color.green;
+
+ m_enabledToggle.onValueChanged.AddListener(OnEnableToggled);
+
+ // layer and scene row
+
+ var sceneLayerRow = UIFactory.CreateHorizontalGroup(scrollContent, new Color(0.1f, 0.1f, 0.1f));
+ var sceneLayerGroup = sceneLayerRow.GetComponent();
+ sceneLayerGroup.childForceExpandWidth = false;
+ sceneLayerGroup.childControlWidth = true;
+ sceneLayerGroup.spacing = 5;
+
+ var layerLabel = UIFactory.CreateLabel(sceneLayerRow, TextAnchor.MiddleCenter);
+ var layerText = layerLabel.GetComponent();
+ layerText.text = "Layer:";
+ layerText.fontSize = 14;
+ layerText.color = Color.grey;
+ var layerTextLayout = layerLabel.AddComponent();
+ layerTextLayout.minWidth = 55;
+ layerTextLayout.flexibleWidth = 0;
+
+ var layerDropdownObj = UIFactory.CreateDropdown(sceneLayerRow, out m_layerDropdown);
+ m_layerDropdown.options.Clear();
+ for (int i = 0; i < 32; i++)
+ {
+ var layer = LayerMaskUnstrip.LayerToName(i);
+ m_layerDropdown.options.Add(new Dropdown.OptionData { text = $"{i}: {layer}" });
+ }
+ //var itemText = layerDropdownObj.transform.Find("Label").GetComponent();
+ //itemText.resizeTextForBestFit = true;
+ var layerDropdownLayout = layerDropdownObj.AddComponent();
+ layerDropdownLayout.minWidth = 120;
+ layerDropdownLayout.flexibleWidth = 2000;
+ layerDropdownLayout.minHeight = 25;
+
+ m_layerDropdown.onValueChanged.AddListener(OnLayerSelected);
+
+ var scenelabelObj = UIFactory.CreateLabel(sceneLayerRow, TextAnchor.MiddleCenter);
+ var sceneLabel = scenelabelObj.GetComponent();
+ sceneLabel.text = "Scene:";
+ sceneLabel.color = Color.grey;
+ sceneLabel.fontSize = 14;
+ var sceneLabelLayout = scenelabelObj.AddComponent();
+ sceneLabelLayout.minWidth = 55;
+ sceneLabelLayout.flexibleWidth = 0;
+
+ var objectSceneText = UIFactory.CreateLabel(sceneLayerRow, TextAnchor.MiddleLeft);
+ m_sceneText = objectSceneText.GetComponent();
+ m_sceneText.fontSize = 14;
+ m_sceneText.horizontalOverflow = HorizontalWrapMode.Overflow;
+ var sceneTextLayout = objectSceneText.AddComponent();
+ sceneTextLayout.minWidth = 120;
+ sceneTextLayout.flexibleWidth = 2000;
+ }
+
+ private GameObject ConstructMidGroup(GameObject parent)
+ {
+ var midGroupObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
+ var midGroup = midGroupObj.GetComponent();
+ midGroup.spacing = 5;
+ midGroup.childForceExpandWidth = true;
+ midGroup.childControlWidth = true;
+ midGroup.childForceExpandHeight = true;
+ midGroup.childControlHeight = true;
+ var midlayout = midGroupObj.AddComponent();
+ midlayout.minHeight = 350;
+ midlayout.flexibleHeight = 10000;
+ midlayout.minWidth = 200;
+ midlayout.flexibleWidth = 25000;
+
+ return midGroupObj;
+ }
+#endregion
+ }
+}
diff --git a/src/Inspectors/InspectorBase.cs b/src/Inspectors/InspectorBase.cs
new file mode 100644
index 0000000..8a3fb25
--- /dev/null
+++ b/src/Inspectors/InspectorBase.cs
@@ -0,0 +1,138 @@
+using System;
+using UnityEngine;
+using UnityEngine.UI;
+using UnityExplorer.Helpers;
+using UnityExplorer.UI;
+
+namespace UnityExplorer.Inspectors
+{
+ public abstract class InspectorBase
+ {
+ public object Target;
+
+ public abstract string TabLabel { get; }
+
+ public bool IsActive { get; private set; }
+ public abstract GameObject Content { get; set; }
+ public Button tabButton;
+ public Text tabText;
+
+ internal bool m_pendingDestroy;
+
+ public InspectorBase(object target)
+ {
+ Target = target;
+
+ if (Target.IsNullOrDestroyed(false))
+ {
+ Destroy();
+ return;
+ }
+
+ AddInspectorTab();
+ }
+
+ public virtual void SetActive()
+ {
+ this.IsActive = true;
+ Content?.SetActive(true);
+ }
+
+ public virtual void SetInactive()
+ {
+ this.IsActive = false;
+ Content?.SetActive(false);
+ }
+
+ public virtual void Update()
+ {
+ if (Target.IsNullOrDestroyed(false))
+ {
+ Destroy();
+ return;
+ }
+
+ tabText.text = TabLabel;
+ }
+
+ public virtual void Destroy()
+ {
+ m_pendingDestroy = true;
+
+ GameObject tabGroup = tabButton?.transform.parent.gameObject;
+
+ if (tabGroup)
+ {
+ GameObject.Destroy(tabGroup);
+ }
+
+ int thisIndex = -1;
+ if (InspectorManager.Instance.m_currentInspectors.Contains(this))
+ {
+ thisIndex = InspectorManager.Instance.m_currentInspectors.IndexOf(this);
+ InspectorManager.Instance.m_currentInspectors.Remove(this);
+ }
+
+ if (ReferenceEquals(InspectorManager.Instance.m_activeInspector, this))
+ {
+ InspectorManager.Instance.UnsetInspectorTab();
+
+ if (InspectorManager.Instance.m_currentInspectors.Count > 0)
+ {
+ var prevTab = InspectorManager.Instance.m_currentInspectors[thisIndex > 0 ? thisIndex - 1 : 0];
+ InspectorManager.Instance.SetInspectorTab(prevTab);
+ }
+ }
+ }
+
+
+
+ #region UI CONSTRUCTION
+
+ public void AddInspectorTab()
+ {
+ var tabContent = InspectorManager.Instance.m_tabBarContent;
+
+ var tabGroupObj = UIFactory.CreateHorizontalGroup(tabContent);
+ var tabGroup = tabGroupObj.GetComponent();
+ tabGroup.childForceExpandWidth = true;
+ tabGroup.childControlWidth = true;
+ var tabLayout = tabGroupObj.AddComponent();
+ tabLayout.minWidth = 185;
+ tabLayout.flexibleWidth = 0;
+ tabGroupObj.AddComponent();
+
+ var targetButtonObj = UIFactory.CreateButton(tabGroupObj);
+ targetButtonObj.AddComponent();
+ var targetButtonLayout = targetButtonObj.AddComponent();
+ targetButtonLayout.minWidth = 165;
+ targetButtonLayout.flexibleWidth = 0;
+
+ tabText = targetButtonObj.GetComponentInChildren();
+ tabText.horizontalOverflow = HorizontalWrapMode.Overflow;
+ tabText.alignment = TextAnchor.MiddleLeft;
+
+ tabButton = targetButtonObj.GetComponent();
+
+ tabButton.onClick.AddListener(() => { InspectorManager.Instance.SetInspectorTab(this); });
+
+ var closeBtnObj = UIFactory.CreateButton(tabGroupObj);
+ var closeBtnLayout = closeBtnObj.AddComponent();
+ closeBtnLayout.minWidth = 20;
+ closeBtnLayout.flexibleWidth = 0;
+ var closeBtnText = closeBtnObj.GetComponentInChildren();
+ closeBtnText.text = "X";
+ closeBtnText.color = new Color(1, 0, 0, 1);
+
+ var closeBtn = closeBtnObj.GetComponent();
+
+ closeBtn.onClick.AddListener(Destroy);
+
+ var closeColors = closeBtn.colors;
+ closeColors.normalColor = new Color(0.2f, 0.2f, 0.2f, 1);
+ closeBtn.colors = closeColors;
+ }
+
+#endregion
+ }
+}
diff --git a/src/Inspectors/InspectorManager.cs b/src/Inspectors/InspectorManager.cs
new file mode 100644
index 0000000..c1261b9
--- /dev/null
+++ b/src/Inspectors/InspectorManager.cs
@@ -0,0 +1,335 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using UnityExplorer.Helpers;
+using UnityExplorer.UI;
+using UnityExplorer.UI.Modules;
+using UnityEngine;
+using UnityEngine.SceneManagement;
+using UnityEngine.UI;
+using UnityExplorer.Inspectors.Reflection;
+
+namespace UnityExplorer.Inspectors
+{
+ public class InspectorManager
+ {
+ public static InspectorManager Instance { get; private set; }
+
+ public InspectorManager()
+ {
+ Instance = this;
+ ConstructInspectorPane();
+ }
+
+ public InspectorBase m_activeInspector;
+ public readonly List m_currentInspectors = new List();
+
+ public GameObject m_tabBarContent;
+ public GameObject m_inspectorContent;
+
+ public void Update()
+ {
+ for (int i = 0; i < m_currentInspectors.Count; i++)
+ {
+ if (i >= m_currentInspectors.Count)
+ break;
+
+ m_currentInspectors[i].Update();
+ }
+ }
+
+ public void Inspect(object obj)
+ {
+#if CPP
+ obj = obj.Il2CppCast(ReflectionHelpers.GetActualType(obj));
+#endif
+ UnityEngine.Object unityObj = obj as UnityEngine.Object;
+
+ if (obj.IsNullOrDestroyed(false))
+ {
+ return;
+ }
+
+ // check if currently inspecting this object
+ foreach (InspectorBase tab in m_currentInspectors)
+ {
+ if (ReferenceEquals(obj, tab.Target))
+ {
+ SetInspectorTab(tab);
+ return;
+ }
+#if CPP
+ else if (unityObj && tab.Target is UnityEngine.Object uTabObj)
+ {
+ if (unityObj.m_CachedPtr == uTabObj.m_CachedPtr)
+ {
+ SetInspectorTab(tab);
+ return;
+ }
+ }
+#endif
+ }
+
+ InspectorBase inspector;
+ if (obj is GameObject go)
+ inspector = new GameObjectInspector(go);
+ else
+ inspector = new InstanceInspector(obj);
+
+ m_currentInspectors.Add(inspector);
+ SetInspectorTab(inspector);
+ }
+
+ public void Inspect(Type type)
+ {
+ if (type == null)
+ {
+ ExplorerCore.LogWarning("The provided type was null!");
+ return;
+ }
+
+ foreach (var tab in m_currentInspectors.Where(x => x is StaticInspector))
+ {
+ if (ReferenceEquals(tab.Target as Type, type))
+ {
+ SetInspectorTab(tab);
+ return;
+ }
+ }
+
+ var inspector = new StaticInspector(type);
+
+ m_currentInspectors.Add(inspector);
+ SetInspectorTab(inspector);
+ }
+
+ public void SetInspectorTab(InspectorBase inspector)
+ {
+ MainMenu.Instance.SetPage(HomePage.Instance);
+
+ if (m_activeInspector == inspector)
+ return;
+
+ UnsetInspectorTab();
+
+ m_activeInspector = inspector;
+ inspector.SetActive();
+
+ Color activeColor = new Color(0, 0.25f, 0, 1);
+ ColorBlock colors = inspector.tabButton.colors;
+ colors.normalColor = activeColor;
+ colors.highlightedColor = activeColor;
+ inspector.tabButton.colors = colors;
+ }
+
+ public void UnsetInspectorTab()
+ {
+ if (m_activeInspector == null)
+ return;
+
+ m_activeInspector.SetInactive();
+
+ ColorBlock colors = m_activeInspector.tabButton.colors;
+ colors.normalColor = new Color(0.2f, 0.2f, 0.2f, 1);
+ colors.highlightedColor = new Color(0.1f, 0.3f, 0.1f, 1);
+ m_activeInspector.tabButton.colors = colors;
+
+ m_activeInspector = null;
+ }
+
+ #region INSPECTOR PANE
+
+ public void ConstructInspectorPane()
+ {
+ var mainObj = UIFactory.CreateVerticalGroup(HomePage.Instance.Content, new Color(72f / 255f, 72f / 255f, 72f / 255f));
+ LayoutElement mainLayout = mainObj.AddComponent();
+ mainLayout.preferredHeight = 400;
+ mainLayout.flexibleHeight = 9000;
+ mainLayout.preferredWidth = 620;
+ mainLayout.flexibleWidth = 9000;
+
+ var mainGroup = mainObj.GetComponent