diff --git a/src/Core/CSharp/ScriptInteraction.cs b/src/Core/CSharp/ScriptInteraction.cs index 2823b93..541da4d 100644 --- a/src/Core/CSharp/ScriptInteraction.cs +++ b/src/Core/CSharp/ScriptInteraction.cs @@ -1,14 +1,12 @@ using System; using Mono.CSharp; -using UnityExplorer.UI; -using UnityExplorer.UI.Main; -using UnityExplorer.Core.Inspectors; -using UnityExplorer.UI.Main.CSConsole; using System.Collections; using UnityEngine; using System.Collections.Generic; using System.Linq; using UnityExplorer.Core.Runtime; +using UnityExplorer.UI.Main.CSConsole; +using UnityExplorer.UI.Main.Home; namespace UnityExplorer.Core.CSharp { diff --git a/src/Core/CSharp/Suggestion.cs b/src/Core/CSharp/Suggestion.cs index 7875f47..2bdd0dc 100644 --- a/src/Core/CSharp/Suggestion.cs +++ b/src/Core/CSharp/Suggestion.cs @@ -4,7 +4,6 @@ using System.Linq; using System.Reflection; using UnityEngine; using UnityExplorer.Core; -using UnityExplorer.Core.Unity; using UnityExplorer.UI.Main.CSConsole; namespace UnityExplorer.Core.CSharp @@ -47,8 +46,8 @@ namespace UnityExplorer.Core.CSharp // ~~~~ Static ~~~~ - public static HashSet Namespaces => m_namspaces ?? GetNamespaces(); - private static HashSet m_namspaces; + public static HashSet Namespaces => m_namespaces ?? GetNamespaces(); + private static HashSet m_namespaces; public static HashSet Keywords => m_keywords ?? (m_keywords = new HashSet(CSLexerHighlighter.validKeywordMatcher.Keywords)); private static HashSet m_keywords; @@ -63,7 +62,7 @@ namespace UnityExplorer.Core.CSharp .Where(x => x.IsPublic && !string.IsNullOrEmpty(x.Namespace)) .Select(x => x.Namespace)); - return m_namspaces = set; + return m_namespaces = set; IEnumerable GetTypes(Assembly asm) => asm.TryGetTypes(); } diff --git a/src/Core/Config/ConfigElement.cs b/src/Core/Config/ConfigElement.cs new file mode 100644 index 0000000..cf53682 --- /dev/null +++ b/src/Core/Config/ConfigElement.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace UnityExplorer.Core.Config +{ + public class ConfigElement : IConfigElement + { + public string Name { get; } + public string Description { get; } + + public bool IsInternal { get; } + public Type ElementType => typeof(T); + + public Action OnValueChanged; + public Action OnValueChangedNotify { get; set; } + + public T Value + { + get => m_value; + set => SetValue(value); + } + private T m_value; + + object IConfigElement.BoxedValue + { + get => m_value; + set => SetValue((T)value); + } + + public ConfigElement(string name, string description, T defaultValue, bool isInternal) + { + Name = name; + Description = description; + + m_value = defaultValue; + + IsInternal = isInternal; + + ConfigManager.RegisterConfigElement(this); + } + + private void SetValue(T value) + { + if ((m_value == null && value == null) || m_value.Equals(value)) + return; + + m_value = value; + + ConfigManager.Handler.SetConfigValue(this, value); + + OnValueChanged?.Invoke(value); + OnValueChangedNotify?.Invoke(); + } + + object IConfigElement.GetLoaderConfigValue() => GetLoaderConfigValue(); + + public T GetLoaderConfigValue() + { + return ConfigManager.Handler.GetConfigValue(this); + } + } +} diff --git a/src/Core/Config/ConfigManager.cs b/src/Core/Config/ConfigManager.cs new file mode 100644 index 0000000..4ee253a --- /dev/null +++ b/src/Core/Config/ConfigManager.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.IO; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityExplorer.UI.Main; +using UnityExplorer.UI.Main.Home; + +namespace UnityExplorer.Core.Config +{ + public static class ConfigManager + { + // Each Loader has its own ConfigHandler. + // See the UnityExplorer.Loader namespace for the implementations. + public static IConfigHandler Handler { get; private set; } + + public static ConfigElement Main_Menu_Toggle; + public static ConfigElement Default_Page_Limit; + public static ConfigElement Default_Output_Path; + public static ConfigElement Log_Unity_Debug; + public static ConfigElement Hide_On_Startup; + public static ConfigElement Last_Window_Anchors; + public static ConfigElement Last_Active_Tab; + public static ConfigElement Last_DebugConsole_State; + public static ConfigElement Last_SceneExplorer_State; + + internal static readonly Dictionary ConfigElements = new Dictionary(); + + public static void Init(IConfigHandler configHandler) + { + Handler = configHandler; + Handler.Init(); + + CreateConfigElements(); + + Handler.LoadConfig(); + + SceneExplorer.OnToggleShow += SceneExplorer_OnToggleShow; + PanelDragger.OnFinishResize += PanelDragger_OnFinishResize; + MainMenu.OnActiveTabChanged += MainMenu_OnActiveTabChanged; + DebugConsole.OnToggleShow += DebugConsole_OnToggleShow; + } + + internal static void RegisterConfigElement(ConfigElement configElement) + { + Handler.RegisterConfigElement(configElement); + ConfigElements.Add(configElement.Name, configElement); + } + + private static void CreateConfigElements() + { + Main_Menu_Toggle = new ConfigElement("Main Menu Toggle", + "The UnityEngine.KeyCode to toggle the UnityExplorer Menu.", + KeyCode.F7, + false); + + Default_Page_Limit = new ConfigElement("Default Page Limit", + "The default maximum number of elements per 'page' in UnityExplorer.", + 25, + false); + + Default_Output_Path = new ConfigElement("Default Output Path", + "The default output path when exporting things from UnityExplorer.", + Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Output"), + false); + + Log_Unity_Debug = new ConfigElement("Log Unity Debug", + "Should UnityEngine.Debug.Log messages be printed to UnityExplorer's log?", + false, + false); + + Hide_On_Startup = new ConfigElement("Hide On Startup", + "Should UnityExplorer be hidden on startup?", + false, + false); + + Last_Window_Anchors = new ConfigElement("Last_Window_Anchors", + "For internal use, the last anchors of the UnityExplorer window.", + DEFAULT_WINDOW_ANCHORS, + true); + + Last_Active_Tab = new ConfigElement("Last_Active_Tab", + "For internal use, the last active tab index.", + 0, + true); + + Last_DebugConsole_State = new ConfigElement("Last_DebugConsole_State", + "For internal use, the collapsed state of the Debug Console.", + true, + true); + + Last_SceneExplorer_State = new ConfigElement("Last_SceneExplorer_State", + "For internal use, the collapsed state of the Scene Explorer.", + true, + true); + } + + // Internal config callback listeners + + private static void PanelDragger_OnFinishResize(RectTransform rect) + { + Last_Window_Anchors.Value = RectAnchorsToString(rect); + Handler.SaveConfig(); + } + + private static void MainMenu_OnActiveTabChanged(int page) + { + Last_Active_Tab.Value = page; + Handler.SaveConfig(); + } + + private static void DebugConsole_OnToggleShow(bool showing) + { + Last_DebugConsole_State.Value = showing; + Handler.SaveConfig(); + } + + private static void SceneExplorer_OnToggleShow(bool showing) + { + Last_SceneExplorer_State.Value = showing; + Handler.SaveConfig(); + } + + // Window Anchors helpers + + private const string DEFAULT_WINDOW_ANCHORS = "0.25,0.10,0.78,0.95"; + + internal static CultureInfo _enCulture = new CultureInfo("en-US"); + + internal static string RectAnchorsToString(this RectTransform rect) + { + try + { + return string.Format(_enCulture, "{0},{1},{2},{3}", new object[] + { + rect.anchorMin.x, + rect.anchorMin.y, + rect.anchorMax.x, + rect.anchorMax.y + }); + } + catch + { + return DEFAULT_WINDOW_ANCHORS; + } + } + + internal static void SetAnchorsFromString(this RectTransform panel, string stringAnchors) + { + Vector4 anchors; + try + { + var split = stringAnchors.Split(','); + + if (split.Length != 4) + throw new Exception(); + + anchors.x = float.Parse(split[0], _enCulture); + anchors.y = float.Parse(split[1], _enCulture); + anchors.z = float.Parse(split[2], _enCulture); + anchors.w = float.Parse(split[3], _enCulture); + } + catch + { + anchors = new Vector4(0.25f, 0.1f, 0.78f, 0.95f); + } + + panel.anchorMin = new Vector2(anchors.x, anchors.y); + panel.anchorMax = new Vector2(anchors.z, anchors.w); + } + } +} diff --git a/src/Core/Config/ExplorerConfig.cs b/src/Core/Config/ExplorerConfig.cs deleted file mode 100644 index bf47096..0000000 --- a/src/Core/Config/ExplorerConfig.cs +++ /dev/null @@ -1,205 +0,0 @@ -using System; -using System.IO; -using UnityEngine; -using IniParser; -using IniParser.Parser; -using UnityExplorer.UI; -using System.Globalization; -using UnityExplorer.Core.Inspectors; -using UnityExplorer.UI.Main; - -namespace UnityExplorer.Core.Config -{ - public class ExplorerConfig - { - public static ExplorerConfig Instance; - - internal static readonly IniDataParser _parser = new IniDataParser(); - internal static readonly string INI_PATH = Path.Combine(ExplorerCore.Loader.ConfigFolder, "config.ini"); - - internal static CultureInfo _enCulture = new CultureInfo("en-US"); - - // Actual configs - public KeyCode Main_Menu_Toggle = KeyCode.F7; - public bool Force_Unlock_Mouse = true; - public int Default_Page_Limit = 25; - public string Default_Output_Path = Path.Combine(ExplorerCore.EXPLORER_FOLDER, "Output"); - public bool Log_Unity_Debug = false; - public bool Hide_On_Startup = false; - public string Window_Anchors = DEFAULT_WINDOW_ANCHORS; - public int Active_Tab = 0; - public bool DebugConsole_Hidden = false; - public bool SceneExplorer_Hidden = false; - - private const string DEFAULT_WINDOW_ANCHORS = "0.25,0.10,0.78,0.95"; - - public static event Action OnConfigChanged; - - internal static void InvokeConfigChanged() - { - OnConfigChanged?.Invoke(); - } - - public static void OnLoad() - { - Instance = new ExplorerConfig(); - _parser.Configuration.CommentString = "#"; - - PanelDragger.OnFinishResize += PanelDragger_OnFinishResize; - SceneExplorer.OnToggleShow += SceneExplorer_OnToggleShow; - DebugConsole.OnToggleShow += DebugConsole_OnToggleShow; - MainMenu.OnActiveTabChanged += MainMenu_OnActiveTabChanged; - - if (LoadSettings()) - return; - - SaveSettings(); - } - - public static bool LoadSettings() - { - if (!File.Exists(INI_PATH)) - return false; - - string ini = File.ReadAllText(INI_PATH); - - var data = _parser.Parse(ini); - - foreach (var config in data.Sections["Config"]) - { - switch (config.KeyName) - { - case nameof(Main_Menu_Toggle): - Instance.Main_Menu_Toggle = (KeyCode)Enum.Parse(typeof(KeyCode), config.Value); - break; - case nameof(Force_Unlock_Mouse): - Instance.Force_Unlock_Mouse = bool.Parse(config.Value); - break; - case nameof(Default_Page_Limit): - Instance.Default_Page_Limit = int.Parse(config.Value); - break; - case nameof(Log_Unity_Debug): - Instance.Log_Unity_Debug = bool.Parse(config.Value); - break; - case nameof(Default_Output_Path): - Instance.Default_Output_Path = config.Value; - break; - case nameof(Hide_On_Startup): - Instance.Hide_On_Startup = bool.Parse(config.Value); - break; - case nameof(Window_Anchors): - Instance.Window_Anchors = config.Value; - break; - case nameof(Active_Tab): - Instance.Active_Tab = int.Parse(config.Value); - break; - case nameof(DebugConsole_Hidden): - Instance.DebugConsole_Hidden = bool.Parse(config.Value); - break; - case nameof(SceneExplorer_Hidden): - Instance.SceneExplorer_Hidden = bool.Parse(config.Value); - break; - } - } - - return true; - } - - public static void SaveSettings() - { - var data = new IniParser.Model.IniData(); - - data.Sections.AddSection("Config"); - - var sec = data.Sections["Config"]; - sec.AddKey(nameof(Main_Menu_Toggle), Instance.Main_Menu_Toggle.ToString()); - sec.AddKey(nameof(Force_Unlock_Mouse), Instance.Force_Unlock_Mouse.ToString()); - sec.AddKey(nameof(Default_Page_Limit), Instance.Default_Page_Limit.ToString()); - sec.AddKey(nameof(Log_Unity_Debug), Instance.Log_Unity_Debug.ToString()); - sec.AddKey(nameof(Default_Output_Path), Instance.Default_Output_Path); - sec.AddKey(nameof(Hide_On_Startup), Instance.Hide_On_Startup.ToString()); - sec.AddKey(nameof(Window_Anchors), GetWindowAnchorsString()); - sec.AddKey(nameof(Active_Tab), Instance.Active_Tab.ToString()); - sec.AddKey(nameof(DebugConsole_Hidden), Instance.DebugConsole_Hidden.ToString()); - sec.AddKey(nameof(SceneExplorer_Hidden), Instance.SceneExplorer_Hidden.ToString()); - - if (!Directory.Exists(ExplorerCore.Loader.ConfigFolder)) - Directory.CreateDirectory(ExplorerCore.Loader.ConfigFolder); - - File.WriteAllText(INI_PATH, data.ToString()); - } - - private static void SceneExplorer_OnToggleShow() - { - Instance.SceneExplorer_Hidden = SceneExplorer.UI.Hiding; - SaveSettings(); - } - - private static void DebugConsole_OnToggleShow() - { - Instance.DebugConsole_Hidden = DebugConsole.Hiding; - SaveSettings(); - } - - private static void MainMenu_OnActiveTabChanged(int page) - { - Instance.Active_Tab = page; - SaveSettings(); - } - - // ============ Window Anchors specific stuff ============== // - - private static void PanelDragger_OnFinishResize() - { - Instance.Window_Anchors = GetWindowAnchorsString(); - SaveSettings(); - } - - internal Vector4 GetWindowAnchorsVector() - { - try - { - var split = Window_Anchors.Split(','); - - if (split.Length != 4) - throw new Exception(); - - Vector4 ret = Vector4.zero; - ret.x = float.Parse(split[0], _enCulture); - ret.y = float.Parse(split[1], _enCulture); - ret.z = float.Parse(split[2], _enCulture); - ret.w = float.Parse(split[3], _enCulture); - return ret; - } - catch - { - return DefaultWindowAnchors(); - } - } - - internal static string GetWindowAnchorsString() - { - try - { - var rect = PanelDragger.Instance.Panel; - return string.Format(_enCulture, "{0},{1},{2},{3}", new object[] - { - rect.anchorMin.x, - rect.anchorMin.y, - rect.anchorMax.x, - rect.anchorMax.y - }); - } - catch - { - return DEFAULT_WINDOW_ANCHORS; - } - } - - internal static Vector4 DefaultWindowAnchors() - { - Instance.Window_Anchors = DEFAULT_WINDOW_ANCHORS; - return new Vector4(0.25f, 0.1f, 0.78f, 0.95f); - } - } -} diff --git a/src/Core/Config/IConfigElement.cs b/src/Core/Config/IConfigElement.cs new file mode 100644 index 0000000..2619b07 --- /dev/null +++ b/src/Core/Config/IConfigElement.cs @@ -0,0 +1,19 @@ +using System; + +namespace UnityExplorer.Core.Config +{ + public interface IConfigElement + { + string Name { get; } + string Description { get; } + + bool IsInternal { get; } + Type ElementType { get; } + + object BoxedValue { get; set; } + + object GetLoaderConfigValue(); + + Action OnValueChangedNotify { get; set; } + } +} diff --git a/src/Core/Config/IConfigHandler.cs b/src/Core/Config/IConfigHandler.cs new file mode 100644 index 0000000..667034f --- /dev/null +++ b/src/Core/Config/IConfigHandler.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace UnityExplorer.Core.Config +{ + public interface IConfigHandler + { + void RegisterConfigElement(ConfigElement element); + + void SetConfigValue(ConfigElement element, T value); + + T GetConfigValue(ConfigElement element); + + void Init(); + + void LoadConfig(); + + void SaveConfig(); + } +} diff --git a/src/UI/Utility/CursorUnlocker.cs b/src/Core/Input/CursorUnlocker.cs similarity index 87% rename from src/UI/Utility/CursorUnlocker.cs rename to src/Core/Input/CursorUnlocker.cs index 80fa54a..a3de30a 100644 --- a/src/UI/Utility/CursorUnlocker.cs +++ b/src/Core/Input/CursorUnlocker.cs @@ -6,30 +6,31 @@ using UnityExplorer.Core.Input; using BF = System.Reflection.BindingFlags; using UnityExplorer.Core.Config; using UnityExplorer.Core; +using UnityExplorer.UI; #if ML using Harmony; #else using HarmonyLib; #endif -namespace UnityExplorer.UI.Utility +namespace UnityExplorer.Core.Input { public class CursorUnlocker { - public static bool Unlock - { - get => m_forceUnlock; - set => SetForceUnlock(value); - } - private static bool m_forceUnlock; + //public static bool Unlock + //{ + // get => m_forceUnlock; + // set => SetForceUnlock(value); + //} + //private static bool m_forceUnlock; - private static void SetForceUnlock(bool unlock) - { - m_forceUnlock = unlock; - UpdateCursorControl(); - } + //private static void SetForceUnlock(bool unlock) + //{ + // m_forceUnlock = unlock; + // UpdateCursorControl(); + //} - public static bool ShouldForceMouse => UIManager.ShowMenu && Unlock; + //public static bool ShouldForceMouse => UIManager.ShowMenu && Unlock; private static CursorLockMode m_lastLockMode; private static bool m_lastVisibleState; @@ -43,16 +44,11 @@ namespace UnityExplorer.UI.Utility public static void Init() { - ExplorerConfig.OnConfigChanged += ModConfig_OnConfigChanged; - SetupPatches(); - Unlock = true; - } + UpdateCursorControl(); - internal static void ModConfig_OnConfigChanged() - { - Unlock = ExplorerConfig.Instance.Force_Unlock_Mouse; + //Unlock = true; } private static void SetupPatches() @@ -67,11 +63,9 @@ namespace UnityExplorer.UI.Utility // Get current cursor state and enable cursor try { - //m_lastLockMode = Cursor.lockState; m_lastLockMode = (CursorLockMode?)typeof(Cursor).GetProperty("lockState", BF.Public | BF.Static)?.GetValue(null, null) ?? CursorLockMode.None; - //m_lastVisibleState = Cursor.visible; m_lastVisibleState = (bool?)typeof(Cursor).GetProperty("visible", BF.Public | BF.Static)?.GetValue(null, null) ?? false; } @@ -105,7 +99,7 @@ namespace UnityExplorer.UI.Utility { var harmony = ExplorerCore.Loader.HarmonyInstance; - System.Reflection.PropertyInfo prop = type.GetProperty(property); + var prop = type.GetProperty(property); if (setter) // setter is prefix { @@ -128,7 +122,7 @@ namespace UnityExplorer.UI.Utility try { m_currentlySettingCursor = true; - if (ShouldForceMouse) + if (UIManager.ShowMenu) { Cursor.lockState = CursorLockMode.None; Cursor.visible = true; @@ -220,7 +214,7 @@ namespace UnityExplorer.UI.Utility { m_lastLockMode = value; - if (ShouldForceMouse) + if (UIManager.ShowMenu) { value = CursorLockMode.None; } @@ -234,7 +228,7 @@ namespace UnityExplorer.UI.Utility { m_lastVisibleState = value; - if (ShouldForceMouse) + if (UIManager.ShowMenu) { value = true; } diff --git a/src/Core/Input/InputManager.cs b/src/Core/Input/InputManager.cs index 5a97992..fcec938 100644 --- a/src/Core/Input/InputManager.cs +++ b/src/Core/Input/InputManager.cs @@ -56,6 +56,8 @@ namespace UnityExplorer.Core.Input m_inputModule = new NoInput(); CurrentType = InputType.None; } + + CursorUnlocker.Init(); } } } \ No newline at end of file diff --git a/src/Core/InspectorManager.cs b/src/Core/InspectorManager.cs deleted file mode 100644 index 1d3683c..0000000 --- a/src/Core/InspectorManager.cs +++ /dev/null @@ -1,138 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityExplorer.Core.Unity; -using UnityExplorer.UI; -using UnityExplorer.UI.Main; -using UnityEngine; -using UnityEngine.SceneManagement; -using UnityEngine.UI; -using UnityExplorer.Core.Inspectors.Reflection; -using UnityExplorer.Core.Runtime; -using UnityExplorer.UI.Main.Home; - -namespace UnityExplorer.Core.Inspectors -{ - public class InspectorManager - { - public static InspectorManager Instance { get; private set; } - - internal static InspectorManagerUI UI; - - public InspectorManager() - { - Instance = this; - - UI = new InspectorManagerUI(); - UI.ConstructInspectorPane(); - } - - public InspectorBase m_activeInspector; - public readonly List m_currentInspectors = new List(); - - 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, CacheObjectBase parentMember = null) - { - obj = ReflectionProvider.Instance.Cast(obj, ReflectionProvider.Instance.GetActualType(obj)); - - 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); - - if (inspector is ReflectionInspector ri) - ri.ParentMember = parentMember; - - 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(); - - UI.OnSetInspectorTab(inspector); - } - - public void UnsetInspectorTab() - { - if (m_activeInspector == null) - return; - - m_activeInspector.SetInactive(); - - UI.OnUnsetInspectorTab(); - - m_activeInspector = null; - } - } -} diff --git a/src/Core/Inspectors/GameObjects/GameObjectInspector.cs b/src/Core/Inspectors/GameObjects/GameObjectInspector.cs deleted file mode 100644 index 179df46..0000000 --- a/src/Core/Inspectors/GameObjects/GameObjectInspector.cs +++ /dev/null @@ -1,101 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityExplorer.UI; -using UnityEngine; -using UnityEngine.UI; -using UnityExplorer.Core.Runtime; -using UnityExplorer.Core.Unity; -using UnityExplorer.UI.Main.Home.Inspectors; - -namespace UnityExplorer.Core.Inspectors -{ - public class GameObjectInspector : InspectorBase - { - public override string TabLabel => $" [G] {TargetGO?.name}"; - - public static GameObjectInspector ActiveInstance { get; private set; } - - public GameObject TargetGO; - - public GameObjectInspectorUI UIModule; - - // sub modules - internal static ChildList s_childList; - internal static ComponentList s_compList; - internal static GameObjectControls s_controls; - - internal static bool m_UIConstructed; - - 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(); - - UIModule.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; - - UIModule.RefreshTopInfo(); - - s_childList.RefreshChildObjectList(); - - s_compList.RefreshComponentList(); - - s_controls.RefreshControls(); - - if (GameObjectControls.s_sliderChangedWanted) - GameObjectControls.UpdateSliderControl(); - } - - public override void CreateUIModule() - { - base.BaseUI = UIModule = new GameObjectInspectorUI(); - } - } -} diff --git a/src/Core/Inspectors/Reflection/InstanceInspector.cs b/src/Core/Inspectors/Reflection/InstanceInspector.cs deleted file mode 100644 index b283169..0000000 --- a/src/Core/Inspectors/Reflection/InstanceInspector.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System; -using System.Reflection; -using UnityEngine; -using UnityExplorer.UI; -using UnityEngine.UI; -using System.IO; -using UnityExplorer.Core.Runtime; -using UnityExplorer.UI.Main.Home.Inspectors; - -namespace UnityExplorer.Core.Inspectors.Reflection -{ - public enum MemberScopes - { - All, - Instance, - Static - } - - public class InstanceInspector : ReflectionInspector - { - public override string TabLabel => $" [R] {base.TabLabel}"; - - internal MemberScopes m_scopeFilter; - internal Button m_lastActiveScopeButton; - - public InstanceInspector(object target) : base(target) { } - - internal InstanceInspectorUI InstanceUI; - public void CreateInstanceUIModule() - { - InstanceUI = new InstanceInspectorUI(this); - } - - internal void OnScopeFilterClicked(MemberScopes type, Button button) - { - if (m_lastActiveScopeButton) - { - var lastColors = m_lastActiveScopeButton.colors; - lastColors.normalColor = new Color(0.2f, 0.2f, 0.2f); - m_lastActiveScopeButton.colors = lastColors; - } - - m_scopeFilter = type; - m_lastActiveScopeButton = button; - - var colors = m_lastActiveScopeButton.colors; - colors.normalColor = new Color(0.2f, 0.6f, 0.2f); - m_lastActiveScopeButton.colors = colors; - - FilterMembers(null, true); - base.ReflectionUI.m_sliderScroller.m_slider.value = 1f; - } - } -} diff --git a/src/Core/ReflectionUtility.cs b/src/Core/ReflectionUtility.cs index ef127f5..b14bced 100644 --- a/src/Core/ReflectionUtility.cs +++ b/src/Core/ReflectionUtility.cs @@ -11,7 +11,7 @@ namespace UnityExplorer.Core { public static class ReflectionUtility { - public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static; + public const BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static; /// /// Helper for IL2CPP to get the underlying true Type (Unhollowed) of the object. @@ -32,7 +32,7 @@ namespace UnityExplorer.Core /// The object to cast /// The object, cast to the underlying Type if possible, otherwise the original object. public static object Cast(this object obj) - => Cast(obj, GetType(obj)); + => ReflectionProvider.Instance.Cast(obj, GetType(obj)); /// /// Cast an object to a Type, if possible. @@ -59,7 +59,10 @@ namespace UnityExplorer.Core public static bool IsDictionary(this Type t) => ReflectionProvider.Instance.IsAssignableFrom(typeof(IDictionary), t); - public static bool LoadModule(string module) + /// + /// [INTERNAL] Used to load Unhollowed DLLs in IL2CPP. + /// + internal static bool LoadModule(string module) => ReflectionProvider.Instance.LoadModule(module); // cache for GetTypeByName diff --git a/src/Core/Runtime/Il2Cpp/ICallManager.cs b/src/Core/Runtime/Il2Cpp/ICallManager.cs index cf0de54..81dc2fb 100644 --- a/src/Core/Runtime/Il2Cpp/ICallManager.cs +++ b/src/Core/Runtime/Il2Cpp/ICallManager.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; +using System.Linq; using System.Runtime.InteropServices; namespace UnityExplorer.Core.Runtime.Il2Cpp @@ -9,6 +10,9 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp [SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")] public static class ICallManager { + [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] + public static extern IntPtr il2cpp_resolve_icall([MarshalAs(UnmanagedType.LPStr)] string name); + private static readonly Dictionary iCallCache = new Dictionary(); /// @@ -26,9 +30,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp IntPtr ptr = il2cpp_resolve_icall(signature); if (ptr == IntPtr.Zero) - { - throw new MissingMethodException($"Could not resolve internal call by name '{signature}'!"); - } + throw new MissingMethodException($"Could not find any iCall with the signature '{signature}'!"); Delegate iCall = Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)); iCallCache.Add(signature, iCall); @@ -36,8 +38,35 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp return (T)iCall; } - [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)] - public static extern IntPtr il2cpp_resolve_icall([MarshalAs(UnmanagedType.LPStr)] string name); + private static readonly Dictionary s_unreliableCache = new Dictionary(); + + /// + /// Get an iCall which may be one of multiple different signatures (ie, it changed in different Unity versions). + /// Each possible signature must have the same Type pattern, it can only vary by name. + /// + public static T GetICallUnreliable(IEnumerable possibleSignatures) where T : Delegate + { + // use the first possible signature as the 'key'. + string key = possibleSignatures.First(); + + if (s_unreliableCache.ContainsKey(key)) + return (T)s_unreliableCache[key]; + + T iCall; + IntPtr ptr; + foreach (var sig in possibleSignatures) + { + ptr = il2cpp_resolve_icall(sig); + if (ptr != IntPtr.Zero) + { + iCall = (T)Marshal.GetDelegateForFunctionPointer(ptr, typeof(T)); + s_unreliableCache.Add(key, iCall); + return iCall; + } + } + + throw new MissingMethodException($"Could not find any iCall from list of provided signatures starting with '{key}'!"); + } } } #endif \ No newline at end of file diff --git a/src/Core/Runtime/Il2Cpp/Il2CppProvider.cs b/src/Core/Runtime/Il2Cpp/Il2CppProvider.cs index 7d87460..0b46760 100644 --- a/src/Core/Runtime/Il2Cpp/Il2CppProvider.cs +++ b/src/Core/Runtime/Il2Cpp/Il2CppProvider.cs @@ -12,6 +12,7 @@ using UnityEngine.Events; using UnityEngine.SceneManagement; using System.Collections; using UnityEngine.UI; +using UnityExplorer.Core.Input; namespace UnityExplorer.Core.Runtime.Il2Cpp { @@ -34,7 +35,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp var addMethod = typeof(Application).GetMethod("add_logMessageReceived", BF.Static | BF.Public, null, new[] { logType }, null); addMethod.Invoke(null, new[] { - castMethod.Invoke(null, new[] { new Action(ExplorerCore.Instance.OnUnityLog) }) + castMethod.Invoke(null, new[] { new Action(Application_logMessageReceived) }) }); } catch @@ -43,11 +44,20 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp } } + private void Application_logMessageReceived(string condition, string stackTrace, LogType type) + { + ExplorerCore.Log(condition, type, true); + } + public override void StartConsoleCoroutine(IEnumerator routine) { Il2CppCoroutine.Start(routine); } + // Unity API Handlers + + // LayerMask.LayerToName + internal delegate IntPtr d_LayerToName(int layer); public override string LayerToName(int layer) @@ -56,20 +66,25 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp return IL2CPP.Il2CppStringToManaged(iCall.Invoke(layer)); } + // Resources.FindObjectsOfTypeAll + internal delegate IntPtr d_FindObjectsOfTypeAll(IntPtr type); public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type) { - var iCall = ICallManager.GetICall("UnityEngine.Resources::FindObjectsOfTypeAll"); - var cppType = Il2CppType.From(type); + var iCall = ICallManager.GetICallUnreliable(new[] + { + "UnityEngine.Resources::FindObjectsOfTypeAll", + "UnityEngine.ResourcesAPIInternal::FindObjectsOfTypeAll" // Unity 2020+ updated to this + }); - return new Il2CppReferenceArray(iCall.Invoke(cppType.Pointer)); + return new Il2CppReferenceArray(iCall.Invoke(Il2CppType.From(type).Pointer)); } public override int GetSceneHandle(Scene scene) => scene.handle; - //Scene.GetRootGameObjects(); + // Scene.GetRootGameObjects(); internal delegate void d_GetRootGameObjects(int handle, IntPtr list); @@ -94,7 +109,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp return list.ToArray(); } - //Scene.rootCount; + // Scene.rootCount internal delegate int d_GetRootCountInternal(int handle); @@ -105,6 +120,36 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp return ICallManager.GetICall("UnityEngine.SceneManagement.Scene::GetRootCountInternal") .Invoke(handle); } + + // ColorBlock set + + public override void SetColorBlockColors(ref ColorBlock colorBlock, Color? normal, Color? highlighted, Color? pressed) + { + if (normal != null) + { + colorBlock.m_NormalColor = (Color)normal; + colorBlock.m_SelectedColor = (Color)normal; + } + + if (highlighted != null) + colorBlock.m_HighlightedColor = (Color)highlighted; + + if (pressed != null) + colorBlock.m_PressedColor = (Color)pressed; + } + + // Custom check for il2cpp input pointer event + + public override void CheckInputPointerEvent() + { + // Some IL2CPP games behave weird with multiple UI Input Systems, some fixes for them. + var evt = InputManager.InputPointerEvent; + if (evt != null) + { + if (!evt.eligibleForClick && evt.selectedObject) + evt.eligibleForClick = true; + } + } } } diff --git a/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs b/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs index 918e73b..1083480 100644 --- a/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs +++ b/src/Core/Runtime/Il2Cpp/Il2CppReflection.cs @@ -5,7 +5,6 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using UnhollowerBaseLib; -using UnityExplorer.Core.Unity; using UnhollowerRuntimeLib; using System.Runtime.InteropServices; using System.Reflection; diff --git a/src/Core/Runtime/Mono/MonoProvider.cs b/src/Core/Runtime/Mono/MonoProvider.cs index d52e5d1..d76567f 100644 --- a/src/Core/Runtime/Mono/MonoProvider.cs +++ b/src/Core/Runtime/Mono/MonoProvider.cs @@ -6,6 +6,7 @@ using System.Linq; using System.Reflection; using System.Text; using UnityEngine; +using UnityEngine.Events; using UnityEngine.SceneManagement; using UnityEngine.UI; using UnityExplorer.Core; @@ -23,9 +24,12 @@ namespace UnityExplorer.Core.Runtime.Mono public override void SetupEvents() { - Application.logMessageReceived += ExplorerCore.Instance.OnUnityLog; - //SceneManager.sceneLoaded += ExplorerCore.Instance.OnSceneLoaded1; - //SceneManager.activeSceneChanged += ExplorerCore.Instance.OnSceneLoaded2; + Application.logMessageReceived += Application_logMessageReceived; + } + + private void Application_logMessageReceived(string condition, string stackTrace, LogType type) + { + ExplorerCore.Log(condition, type, true); } public override void StartConsoleCoroutine(IEnumerator routine) @@ -55,11 +59,38 @@ namespace UnityExplorer.Core.Runtime.Mono { return scene.rootCount; } + + public override void SetColorBlockColors(ref ColorBlock block, Color? normal, Color? highlighted, Color? pressed) + { + if (normal != null) + block.normalColor = (Color)normal; + + if (highlighted != null) + block.highlightedColor = (Color)highlighted; + + if (pressed != null) + block.pressedColor = (Color)pressed; + } + + public override void CheckInputPointerEvent() + { + // Not necessary afaik + } } } public static class MonoExtensions { + public static void AddListener(this UnityEvent _event, Action listener) + { + _event.AddListener(new UnityAction(listener)); + } + + public static void AddListener(this UnityEvent _event, Action listener) + { + _event.AddListener(new UnityAction(listener)); + } + public static void Clear(this StringBuilder sb) { sb.Remove(0, sb.Length); diff --git a/src/Core/Runtime/RuntimeProvider.cs b/src/Core/Runtime/RuntimeProvider.cs index 16be287..98af1c0 100644 --- a/src/Core/Runtime/RuntimeProvider.cs +++ b/src/Core/Runtime/RuntimeProvider.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using UnityEngine; using UnityEngine.SceneManagement; +using UnityEngine.UI; namespace UnityExplorer.Core.Runtime { @@ -50,5 +51,9 @@ namespace UnityExplorer.Core.Runtime public abstract GameObject[] GetRootGameObjects(Scene scene); public abstract int GetRootCount(Scene scene); + + public abstract void SetColorBlockColors(ref ColorBlock block, Color? normal, Color? highlight, Color? pressed); + + public abstract void CheckInputPointerEvent(); } } diff --git a/src/Core/SceneExplorer.cs b/src/Core/SceneExplorer.cs deleted file mode 100644 index c292f09..0000000 --- a/src/Core/SceneExplorer.cs +++ /dev/null @@ -1,206 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using UnityExplorer.UI; -using UnityExplorer.UI.Main; -using UnityExplorer.UI.Reusable; -using UnityEngine; -using UnityEngine.SceneManagement; -using UnityEngine.UI; -using UnityExplorer.Core.Runtime; -using UnityExplorer.UI.Main.Home; -using UnityExplorer.Core.Config; - -namespace UnityExplorer.Core.Inspectors -{ - public class SceneExplorer - { - public static SceneExplorer Instance; - - public static SceneExplorerUI UI; - - internal static Action OnToggleShow; - - public SceneExplorer() - { - Instance = this; - - UI = new SceneExplorerUI(); - UI.ConstructScenePane(); - } - - private const float UPDATE_INTERVAL = 1f; - private float m_timeOfLastSceneUpdate; - - // private int m_currentSceneHandle = -1; - public static Scene DontDestroyScene => DontDestroyObject.scene; - internal Scene m_currentScene; - internal Scene[] m_currentScenes = new Scene[0]; - - internal GameObject[] m_allObjects = new GameObject[0]; - - internal GameObject m_selectedSceneObject; - internal int m_lastCount; - - internal static GameObject DontDestroyObject - { - get - { - if (!s_dontDestroyObject) - { - s_dontDestroyObject = new GameObject("DontDestroyMe"); - GameObject.DontDestroyOnLoad(s_dontDestroyObject); - } - return s_dontDestroyObject; - } - } - internal static GameObject s_dontDestroyObject; - - public void Init() - { - RefreshSceneSelector(); - - if (ExplorerConfig.Instance.SceneExplorer_Hidden) - UI.ToggleShow(); - } - - public void Update() - { - if (UI.Hiding || Time.realtimeSinceStartup - m_timeOfLastSceneUpdate < UPDATE_INTERVAL) - return; - - RefreshSceneSelector(); - - if (!m_selectedSceneObject) - { - if (m_currentScene != default) - { - var rootObjects = RuntimeProvider.Instance.GetRootGameObjects(m_currentScene); - SetSceneObjectList(rootObjects); - } - } - else - { - RefreshSelectedSceneObject(); - } - } - - private void RefreshSceneSelector() - { - var newNames = new List(); - var newScenes = new List(); - - if (m_currentScenes == null) - m_currentScenes = new Scene[0]; - - bool anyChange = SceneManager.sceneCount != m_currentScenes.Length - 1; - - for (int i = 0; i < SceneManager.sceneCount; i++) - { - Scene scene = SceneManager.GetSceneAt(i); - - if (scene == default) - continue; - - int handle = RuntimeProvider.Instance.GetSceneHandle(scene); - - if (!anyChange && !m_currentScenes.Any(it => handle == RuntimeProvider.Instance.GetSceneHandle(it))) - anyChange = true; - - newScenes.Add(scene); - newNames.Add(scene.name); - } - - if (anyChange) - { - newNames.Add("DontDestroyOnLoad"); - newScenes.Add(DontDestroyScene); - m_currentScenes = newScenes.ToArray(); - - UI.OnActiveScenesChanged(newNames); - - SetTargetScene(newScenes[0]); - - SearchPage.Instance.OnSceneChange(); - } - } - - public void SetTargetScene(int index) - => SetTargetScene(m_currentScenes[index]); - - public void SetTargetScene(Scene scene) - { - if (scene == default) - return; - - m_currentScene = scene; - var rootObjs = RuntimeProvider.Instance.GetRootGameObjects(scene); - SetSceneObjectList(rootObjs); - - m_selectedSceneObject = null; - - UI.OnSceneSelected(); - } - - public void SetSceneObjectParent() - { - if (!m_selectedSceneObject || !m_selectedSceneObject.transform.parent?.gameObject) - { - m_selectedSceneObject = null; - SetTargetScene(m_currentScene); - } - else - { - SetTargetObject(m_selectedSceneObject.transform.parent.gameObject); - } - } - - public void SetTargetObject(GameObject obj) - { - if (!obj) - return; - - UI.OnGameObjectSelected(obj); - - m_selectedSceneObject = obj; - - RefreshSelectedSceneObject(); - } - - private void RefreshSelectedSceneObject() - { - GameObject[] list = new GameObject[m_selectedSceneObject.transform.childCount]; - for (int i = 0; i < m_selectedSceneObject.transform.childCount; i++) - { - list[i] = m_selectedSceneObject.transform.GetChild(i).gameObject; - } - - SetSceneObjectList(list); - } - - private void SetSceneObjectList(GameObject[] objects) - { - m_allObjects = objects; - RefreshSceneObjectList(); - } - - internal void RefreshSceneObjectList() - { - m_timeOfLastSceneUpdate = Time.realtimeSinceStartup; - - UI.RefreshSceneObjectList(m_allObjects, out int newCount); - - m_lastCount = newCount; - } - - internal static void InspectSelectedGameObject() - { - InspectorManager.Instance.Inspect(Instance.m_selectedSceneObject); - } - - internal static void InvokeOnToggleShow() - { - OnToggleShow?.Invoke(); - } - } -} diff --git a/src/Core/Search/ChildFilter.cs b/src/Core/Search/ChildFilter.cs index 33d1bc7..59b63a3 100644 --- a/src/Core/Search/ChildFilter.cs +++ b/src/Core/Search/ChildFilter.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; -namespace UnityExplorer.Search +namespace UnityExplorer.Core.Search { internal enum ChildFilter { diff --git a/src/Core/Search/SceneFilter.cs b/src/Core/Search/SceneFilter.cs index ff6a9a3..3373b4e 100644 --- a/src/Core/Search/SceneFilter.cs +++ b/src/Core/Search/SceneFilter.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; -namespace UnityExplorer.Search +namespace UnityExplorer.Core.Search { internal enum SceneFilter { diff --git a/src/Core/Search/SearchContext.cs b/src/Core/Search/SearchContext.cs index a09a33e..982be2f 100644 --- a/src/Core/Search/SearchContext.cs +++ b/src/Core/Search/SearchContext.cs @@ -3,7 +3,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; -namespace UnityExplorer.Search +namespace UnityExplorer.Core.Search { internal enum SearchContext { diff --git a/src/Core/Search/SearchProvider.cs b/src/Core/Search/SearchProvider.cs index 7c3bdbf..5ae6301 100644 --- a/src/Core/Search/SearchProvider.cs +++ b/src/Core/Search/SearchProvider.cs @@ -4,11 +4,11 @@ using System.Linq; using System.Reflection; using System.Text; using UnityEngine; -using UnityExplorer.Core; using UnityExplorer.Core.Runtime; using UnityExplorer.UI.Main; +using UnityExplorer.UI.Main.Search; -namespace UnityExplorer.Search +namespace UnityExplorer.Core.Search { public static class SearchProvider { diff --git a/src/Core/Tests/Tests.cs b/src/Core/Tests/Tests.cs deleted file mode 100644 index 7c38067..0000000 --- a/src/Core/Tests/Tests.cs +++ /dev/null @@ -1,290 +0,0 @@ -//using System.Collections; -//using System.Collections.Generic; -//using UnityExplorer.UI; -//using UnityEngine; -//using System; -//using System.Runtime.InteropServices; -//using System.Text; -//using UnityExplorer.Core.Runtime; - -//namespace UnityExplorer.Core.Tests -//{ -// internal enum TestByteEnum : byte -// { -// One, -// Two, -// Three, -// TwoFiftyFive = 255, -// } - -// public static class StaticTestClass -// { -// public static int StaticProperty => 5; -// public static int StaticField = 69; -// public static List StaticList = new List -// { -// "one", -// "two", -// "three", -// }; -// public static void StaticMethod() { } -// } - -// public class TestClass -// { -// internal static TestByteEnum testingByte = TestByteEnum.One; - -// public string AAALongString = @"1 -//2 -//3 -//4 -//5"; - -// public Vector2 AATestVector2 = new Vector2(1, 2); -// public Vector3 AATestVector3 = new Vector3(1, 2, 3); -// public Vector4 AATestVector4 = new Vector4(1, 2, 3, 4); -// public Rect AATestRect = new Rect(1, 2, 3, 4); -// public Color AATestColor = new Color(0.1f, 0.2f, 0.3f, 0.4f); - -// public bool ATestBoolMethod() => false; - -// public bool this[int index] -// { -// get => index % 2 == 0; -// set => m_thisBool = value; -// } -// internal bool m_thisBool; - -// static int testInt; -// public static List ExceptionList -// { -// get -// { -// testInt++; -// if (testInt % 2 == 0) -// throw new Exception("its even"); -// else -// return new List { "one" }; -// } -// } - -// static bool abool; -// public static bool ATestExceptionBool -// { -// get -// { -// abool = !abool; -// if (!abool) -// throw new Exception("false"); -// else -// return true; -// } -// } - -// public static string ExceptionString => throw new NotImplementedException(); - -// public static string ANullString = null; -// public static float ATestFloat = 420.69f; -// public static int ATestInt = -1; -// public static string ATestString = "hello world"; -// public static uint ATestUInt = 1u; -// public static byte ATestByte = 255; -// public static ulong AReadonlyUlong = 82934UL; - -// public static TestClass Instance => m_instance ?? (m_instance = new TestClass()); -// private static TestClass m_instance; - -// public object AmbigObject; - -// public List>> ANestedNestedList = new List>> -// { -// new List> -// { -// new List -// { -// "one", -// "two", -// }, -// new List -// { -// "three", -// "four" -// } -// }, -// new List> -// { -// new List -// { -// "five", -// "six" -// } -// } -// }; - -// public static bool SetOnlyProperty -// { -// set => m_setOnlyProperty = value; -// } -// private static bool m_setOnlyProperty; -// public static bool ReadSetOnlyProperty => m_setOnlyProperty; - -// public Texture2D TestTexture; -// public static Sprite TestSprite; - -//#if CPP -// public static Il2CppSystem.Collections.Generic.HashSet CppHashSetTest; -// public static Il2CppSystem.Collections.Generic.List CppStringTest; -// public static Il2CppSystem.Collections.IList CppIList; -// //public static Il2CppSystem.Collections.Generic.Dictionary CppDictTest; -// //public static Il2CppSystem.Collections.Generic.Dictionary CppDictTest2; -//#endif - -// public TestClass() -// { -// int a = 0; -// foreach (var list in ANestedNestedList) -// { -// foreach (var list2 in list) -// { -// for (int i = 0; i < 33; i++) -// list2.Add(a++.ToString()); -// } -// } - -//#if CPP -// //TextureSpriteTest(); - -// CppHashSetTest = new Il2CppSystem.Collections.Generic.HashSet(); -// CppHashSetTest.Add("1"); -// CppHashSetTest.Add("2"); -// CppHashSetTest.Add("3"); - -// CppStringTest = new Il2CppSystem.Collections.Generic.List(); -// CppStringTest.Add("1"); -// CppStringTest.Add("2"); - -// //CppDictTest = new Il2CppSystem.Collections.Generic.Dictionary(); -// //CppDictTest.Add("key1", "value1"); -// //CppDictTest.Add("key2", "value2"); -// //CppDictTest.Add("key3", "value3"); - -// //CppDictTest2 = new Il2CppSystem.Collections.Generic.Dictionary(); -// //CppDictTest2.Add(0, 0.5f); -// //CppDictTest2.Add(1, 0.5f); -// //CppDictTest2.Add(2, 0.5f); -//#endif -// } - -// //private void TextureSpriteTest() -// //{ -// // TestTexture = new Texture2D(32, 32, TextureFormat.ARGB32, false) -// // { -// // name = "TestTexture" -// // }; -// // TestSprite = TextureUtilProvider.Instance.CreateSprite(TestTexture); - -// // GameObject.DontDestroyOnLoad(TestTexture); -// // GameObject.DontDestroyOnLoad(TestSprite); - -// // // test loading a tex from file -// // if (System.IO.File.Exists(@"D:\Downloads\test.png")) -// // { -// // var dataToLoad = System.IO.File.ReadAllBytes(@"D:\Downloads\test.png"); -// // ExplorerCore.Log($"Tex load success: {TestTexture.LoadImage(dataToLoad, false)}"); -// // } -// //} - -// //public static string TestRefInOutGeneric(ref string arg0, in int arg1, out string arg2) where T : Component -// //{ -// // arg2 = "this is arg2"; - -// // return $"T: '{typeof(T).FullName}', ref arg0: '{arg0}', in arg1: '{arg1}', out arg2: '{arg2}'"; -// //} - -// // test a non-generic dictionary - -// public Hashtable TestNonGenericDict() -// { -// return new Hashtable -// { -// { "One", 1 }, -// { "Two", 2 }, -// { "Three", 3 }, -// }; -// } - -// // test HashSets - -// public static HashSet HashSetTest = new HashSet -// { -// "One", -// "Two", -// "Three" -// }; - - -// // Test indexed parameter - -// public string this[int arg0, string arg1] -// { -// get -// { -// return $"arg0: {arg0}, arg1: {arg1}"; -// } -// } - -// // Test basic list - -// public static List TestList = new List -// { -// "1", -// "2", -// "3", -// "etc..." -// }; - -// // Test a nested dictionary - -// public static Dictionary> NestedDictionary = new Dictionary> -// { -// { -// 1, -// new Dictionary -// { -// { -// "Sub 1", 123 -// }, -// { -// "Sub 2", 456 -// }, -// } -// }, -// { -// 2, -// new Dictionary -// { -// { -// "Sub 3", 789 -// }, -// { -// "Sub 4", 000 -// }, -// } -// }, -// }; - -// // Test a basic method - -// public static Color TestMethod(float r, float g, float b, float a) -// { -// return new Color(r, g, b, a); -// } - -// // A method with default arguments - -// public static Vector3 TestDefaultArgs(float arg0, float arg1, float arg2 = 5.0f) -// { -// return new Vector3(arg0, arg1, arg2); -// } -// } -//} diff --git a/src/Core/Unity/ColorHelper.cs b/src/Core/Unity/ColorHelper.cs deleted file mode 100644 index cd234c6..0000000 --- a/src/Core/Unity/ColorHelper.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Globalization; -using UnityEngine; - -namespace UnityExplorer.Core.Unity -{ - public static class ColorHelper - { - /// - /// Converts Color to 6-digit RGB hex code (without # symbol). Eg, RGBA(1,0,0,1) -> FF0000 - /// - public static string ToHex(this Color color) - { - byte r = (byte)Mathf.Clamp(Mathf.RoundToInt(color.r * 255f), 0, 255); - byte g = (byte)Mathf.Clamp(Mathf.RoundToInt(color.g * 255f), 0, 255); - byte b = (byte)Mathf.Clamp(Mathf.RoundToInt(color.b * 255f), 0, 255); - - return $"{r:X2}{g:X2}{b:X2}"; - } - - /// - /// Assumes the string is a 6-digit RGB Hex color code, which it will parse into a UnityEngine.Color. - /// Eg, FF0000 -> RGBA(1,0,0,1) - /// - public static Color ToColor(this string _string) - { - _string = _string.Replace("#", ""); - - if (_string.Length != 6) - return Color.magenta; - - var r = byte.Parse(_string.Substring(0, 2), NumberStyles.HexNumber); - var g = byte.Parse(_string.Substring(2, 2), NumberStyles.HexNumber); - var b = byte.Parse(_string.Substring(4, 2), NumberStyles.HexNumber); - - var color = new Color - { - r = (float)(r / (decimal)255), - g = (float)(g / (decimal)255), - b = (float)(b / (decimal)255), - a = 1 - }; - - return color; - } - } -} diff --git a/src/Core/Unity/UnityHelper.cs b/src/Core/Unity/UnityHelper.cs deleted file mode 100644 index f95231d..0000000 --- a/src/Core/Unity/UnityHelper.cs +++ /dev/null @@ -1,62 +0,0 @@ -using UnityEngine; - -namespace UnityExplorer.Core.Unity -{ - public static class UnityHelper - { - private static Camera m_mainCamera; - - public static Camera MainCamera - { - get - { - if (!m_mainCamera) - { - m_mainCamera = Camera.main; - } - return m_mainCamera; - } - } - - public static bool IsNullOrDestroyed(this object obj, bool suppressWarning = true) - { - var unityObj = obj as Object; - if (obj == null) - { - 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/Core/Unity/UnityHelpers.cs b/src/Core/Unity/UnityHelpers.cs new file mode 100644 index 0000000..fc401c1 --- /dev/null +++ b/src/Core/Unity/UnityHelpers.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace UnityExplorer.Core.Unity +{ + public static class UnityHelpers + { + /// + /// Check if an object is null, and if it's a UnityEngine.Object then also check if it was destroyed. + /// + public static bool IsNullOrDestroyed(this object obj, bool suppressWarning = true) + { + var unityObj = obj as Object; + if (obj == null) + { + 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; + } + + /// + /// Print a nice {X, Y, Z} output of the Vector3, formatted to 3 decimal places. + /// + public static string ToStringPretty(this Vector3 vec) + { + return $"{vec.x:F3}, {vec.y:F3}, {vec.z:F3}"; + } + + /// + /// Get the full Transform heirarchy path for this provided Transform. + /// + public static string GetTransformPath(this Transform transform, bool includeSelf = false) + { + string path = includeSelf + ? transform.transform.name + : ""; + + while (transform.parent) + { + transform = transform.parent; + path = $"{transform.name}/{path}"; + } + + return path; + } + + /// + /// Converts Color to 6-digit RGB hex code (without # symbol). Eg, RGBA(1,0,0,1) -> FF0000 + /// + public static string ToHex(this Color color) + { + byte r = (byte)Mathf.Clamp(Mathf.RoundToInt(color.r * 255f), 0, 255); + byte g = (byte)Mathf.Clamp(Mathf.RoundToInt(color.g * 255f), 0, 255); + byte b = (byte)Mathf.Clamp(Mathf.RoundToInt(color.b * 255f), 0, 255); + + return $"{r:X2}{g:X2}{b:X2}"; + } + + /// + /// Assumes the string is a 6-digit RGB Hex color code, which it will parse into a UnityEngine.Color. + /// Eg, FF0000 -> RGBA(1,0,0,1) + /// + public static Color ToColor(this string _string) + { + _string = _string.Replace("#", ""); + + if (_string.Length != 6) + return Color.magenta; + + var r = byte.Parse(_string.Substring(0, 2), NumberStyles.HexNumber); + var g = byte.Parse(_string.Substring(2, 2), NumberStyles.HexNumber); + var b = byte.Parse(_string.Substring(4, 2), NumberStyles.HexNumber); + + var color = new Color + { + r = (float)(r / (decimal)255), + g = (float)(g / (decimal)255), + b = (float)(b / (decimal)255), + a = 1 + }; + + return color; + } + } +} diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index e5dcae2..bb66139 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -1,126 +1,93 @@ using System; using System.IO; -using System.Reflection; using UnityEngine; -using UnityEngine.SceneManagement; using UnityExplorer.Core.Config; -using UnityExplorer.Core.Unity; using UnityExplorer.Core.Input; -using UnityExplorer.Core.Inspectors; using UnityExplorer.Core.Runtime; using UnityExplorer.UI; using UnityExplorer.UI.Main; -using UnityExplorer.UI.Utility; namespace UnityExplorer { public class ExplorerCore { public const string NAME = "UnityExplorer"; - public const string VERSION = "3.2.10"; + public const string VERSION = "3.3.0"; public const string AUTHOR = "Sinai"; public const string GUID = "com.sinai.unityexplorer"; public static ExplorerCore Instance { get; private set; } - public static IExplorerLoader Loader => -#if ML - ExplorerMelonMod.Instance; -#elif BIE - ExplorerBepInPlugin.Instance; -#elif STANDALONE - ExplorerStandalone.Instance; -#endif + public static IExplorerLoader Loader { get; private set; } - public static string EXPLORER_FOLDER => Loader.ExplorerFolder; + // Prevent using ctor, must use Init method. + private ExplorerCore() { } - public ExplorerCore() + public static void Init(IExplorerLoader loader) { if (Instance != null) { - Log("An instance of Explorer is already active!"); + Log("An instance of UnityExplorer is already active!"); return; } - Instance = this; + Loader = loader; + Instance = new ExplorerCore(); - if (!Directory.Exists(EXPLORER_FOLDER)) - Directory.CreateDirectory(EXPLORER_FOLDER); + if (!Directory.Exists(Loader.ExplorerFolder)) + Directory.CreateDirectory(Loader.ExplorerFolder); - ExplorerConfig.OnLoad(); + ConfigManager.Init(Loader.ConfigHandler); RuntimeProvider.Init(); InputManager.Init(); - CursorUnlocker.Init(); - - UIManager.ShowMenu = true; + UIManager.Init(); Log($"{NAME} {VERSION} initialized."); } public static void Update() { - UIManager.CheckUIInit(); - - if (InspectUnderMouse.Enabled) - InspectUnderMouse.UpdateInspect(); - else - UIManager.Update(); + UIManager.Update(); } - public void OnUnityLog(string message, string stackTrace, LogType type) + public static void Log(object message) + => Log(message, LogType.Log, false); + + public static void LogWarning(object message) + => Log(message, LogType.Warning, false); + + public static void LogError(object message) + => Log(message, LogType.Error, false); + + internal static void Log(object message, LogType logType, bool isFromUnity = false) { - if (!DebugConsole.LogUnity) + if (isFromUnity && !ConfigManager.Log_Unity_Debug.Value) return; - message = $"[UNITY] {message}"; + string log = message?.ToString() ?? ""; - switch (type) + switch (logType) { case LogType.Assert: case LogType.Log: - Log(message, true); + Loader.OnLogMessage(log); + DebugConsole.Log(log, Color.white); break; + case LogType.Warning: - LogWarning(message, true); + Loader.OnLogWarning(log); + DebugConsole.Log(log, Color.yellow); break; - case LogType.Exception: + case LogType.Error: - LogError(message, true); + case LogType.Exception: + Loader.OnLogError(log); + DebugConsole.Log(log, Color.red); break; } } - - public static void Log(object message, bool unity = false) - { - DebugConsole.Log(message?.ToString()); - - if (unity) - return; - - Loader.OnLogMessage(message); - } - - public static void LogWarning(object message, bool unity = false) - { - DebugConsole.Log(message?.ToString(), "FFFF00"); - - if (unity) - return; - - Loader.OnLogWarning(message); - } - - public static void LogError(object message, bool unity = false) - { - DebugConsole.Log(message?.ToString(), "FF0000"); - - if (unity) - return; - - Loader.OnLogError(message); - } } } diff --git a/src/ILRepack.targets b/src/ILRepack.targets index 8c17eca..c1e668f 100644 --- a/src/ILRepack.targets +++ b/src/ILRepack.targets @@ -1,22 +1,20 @@ - - - - - - - - - - - + + + + + + + + + + \ No newline at end of file diff --git a/src/Loader/BIE/BepInExConfigHandler.cs b/src/Loader/BIE/BepInExConfigHandler.cs new file mode 100644 index 0000000..a47399f --- /dev/null +++ b/src/Loader/BIE/BepInExConfigHandler.cs @@ -0,0 +1,69 @@ +#if BIE +using BepInEx.Configuration; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityExplorer.Core.Config; + +namespace UnityExplorer.Loader.BIE +{ + public class BepInExConfigHandler : IConfigHandler + { + private ConfigFile Config => ExplorerBepInPlugin.Instance.Config; + + private const string CTG_NAME = "UnityExplorer"; + + public void Init() + { + // Not necessary + } + + public void RegisterConfigElement(ConfigElement config) + { + var entry = Config.Bind(CTG_NAME, config.Name, config.Value, config.Description); + + entry.SettingChanged += (object o, EventArgs e) => + { + config.Value = entry.Value; + }; + } + + public T GetConfigValue(ConfigElement element) + { + if (Config.TryGetEntry(CTG_NAME, element.Name, out ConfigEntry configEntry)) + return configEntry.Value; + else + throw new Exception("Could not get config entry '" + element.Name + "'"); + } + + public void SetConfigValue(ConfigElement element, T value) + { + if (Config.TryGetEntry(CTG_NAME, element.Name, out ConfigEntry configEntry)) + configEntry.Value = value; + else + ExplorerCore.Log("Could not get config entry '" + element.Name + "'"); + } + + public void LoadConfig() + { + foreach (var entry in ConfigManager.ConfigElements) + { + var key = entry.Key; + var def = new ConfigDefinition(CTG_NAME, key); + if (Config.ContainsKey(def) && Config[def] is ConfigEntryBase configEntry) + { + var config = entry.Value; + config.BoxedValue = configEntry.BoxedValue; + } + } + } + + public void SaveConfig() + { + // not required + } + } +} + +#endif \ No newline at end of file diff --git a/src/Loader/BIE/ExplorerBepInPlugin.cs b/src/Loader/BIE/ExplorerBepInPlugin.cs new file mode 100644 index 0000000..0a8127f --- /dev/null +++ b/src/Loader/BIE/ExplorerBepInPlugin.cs @@ -0,0 +1,106 @@ +#if BIE +using BepInEx; +using BepInEx.Logging; +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using UnityExplorer.Core.Config; +using UnityExplorer.Loader.BIE; +using UnityEngine; +#if CPP +using BepInEx.IL2CPP; +using UnhollowerRuntimeLib; +#endif + +namespace UnityExplorer +{ + [BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)] + + public class ExplorerBepInPlugin : +#if MONO + BaseUnityPlugin +#else + BasePlugin +#endif + , IExplorerLoader + { + public static ExplorerBepInPlugin Instance; + + public ManualLogSource LogSource +#if MONO + => Logger; +#else + => Log; +#endif + + public IConfigHandler ConfigHandler => _configHandler; + private BepInExConfigHandler _configHandler; + + public Harmony HarmonyInstance => s_harmony; + private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID); + + public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME); + public string ConfigFolder => Path.Combine(Paths.ConfigPath, ExplorerCore.NAME); + + public Action OnLogMessage => LogSource.LogMessage; + public Action OnLogWarning => LogSource.LogWarning; + public Action OnLogError => LogSource.LogError; + + // Init common to Mono and Il2Cpp + internal void UniversalInit() + { + Instance = this; + _configHandler = new BepInExConfigHandler(); + } + +#if MONO // Mono-specific + internal void Awake() + { + UniversalInit(); + ExplorerCore.Init(this); + } + + internal void Update() + { + ExplorerCore.Update(); + } + +#else // Il2Cpp-specific + public override void Load() + { + UniversalInit(); + + ClassInjector.RegisterTypeInIl2Cpp(); + + var obj = new GameObject( + "ExplorerBehaviour", + new Il2CppSystem.Type[] { Il2CppType.Of() } + ); + obj.hideFlags = HideFlags.HideAndDontSave; + GameObject.DontDestroyOnLoad(obj); + + ExplorerCore.Init(this); + } + + // 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) { } + + internal void Awake() + { + Instance.LogSource.LogMessage("ExplorerBehaviour.Awake"); + } + + internal void Update() + { + ExplorerCore.Update(); + } + } +#endif + } +} +#endif \ No newline at end of file diff --git a/src/Loader/ExplorerBepIn5Plugin.cs b/src/Loader/ExplorerBepIn5Plugin.cs deleted file mode 100644 index 26146ea..0000000 --- a/src/Loader/ExplorerBepIn5Plugin.cs +++ /dev/null @@ -1,41 +0,0 @@ -#if BIE5 -using System; -using System.IO; -using System.Reflection; -using BepInEx; -using BepInEx.Logging; -using HarmonyLib; - -namespace UnityExplorer -{ - [BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)] - public class ExplorerBepInPlugin : BaseUnityPlugin, IExplorerLoader - { - public static ExplorerBepInPlugin Instance; - - public static ManualLogSource Logging => Instance?.Logger; - - public Harmony HarmonyInstance => s_harmony; - private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID); - - public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME); - public string ConfigFolder => Path.Combine(Paths.ConfigPath, ExplorerCore.NAME); - - public Action OnLogMessage => (object log) => { Logging?.LogMessage(log?.ToString() ?? ""); }; - public Action OnLogWarning => (object log) => { Logging?.LogWarning(log?.ToString() ?? ""); }; - public Action OnLogError => (object log) => { Logging?.LogError(log?.ToString() ?? ""); }; - - internal void Awake() - { - Instance = this; - - new ExplorerCore(); - } - - internal void Update() - { - ExplorerCore.Update(); - } - } -} -#endif diff --git a/src/Loader/ExplorerBepIn6Plugin.cs b/src/Loader/ExplorerBepIn6Plugin.cs deleted file mode 100644 index b159341..0000000 --- a/src/Loader/ExplorerBepIn6Plugin.cs +++ /dev/null @@ -1,104 +0,0 @@ -#if BIE6 -using System; -using System.IO; -using System.Reflection; -using BepInEx; -using BepInEx.Logging; -using HarmonyLib; -using UnityEngine; -using UnityEngine.SceneManagement; -using UnityEngine.UI; -using UnityExplorer.UI.Main; -#if CPP -using UnhollowerRuntimeLib; -using BepInEx.IL2CPP; -#endif - -namespace UnityExplorer -{ -#if MONO - [BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)] - public class ExplorerBepInPlugin : BaseUnityPlugin, IExplorerLoader - { - public static ExplorerBepInPlugin Instance; - - public static ManualLogSource Logging => Instance?.Logger; - - public Harmony HarmonyInstance => s_harmony; - private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID); - - public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME); - public string ConfigFolder => Path.Combine(Paths.ConfigPath, ExplorerCore.NAME); - - public Action OnLogMessage => (object log) => { Logging?.LogMessage(log?.ToString() ?? ""); }; - public Action OnLogWarning => (object log) => { Logging?.LogWarning(log?.ToString() ?? ""); }; - public Action OnLogError => (object log) => { Logging?.LogError(log?.ToString() ?? ""); }; - - internal void Awake() - { - Instance = this; - - new ExplorerCore(); - } - - internal void Update() - { - ExplorerCore.Update(); - } - } -#endif - -#if CPP - [BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)] - public class ExplorerBepInPlugin : BasePlugin, IExplorerLoader - { - public static ExplorerBepInPlugin Instance; - - public static ManualLogSource Logging => Instance?.Log; - - public Harmony HarmonyInstance => s_harmony; - private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID); - - public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME); - public string ConfigFolder => Path.Combine(Paths.ConfigPath, ExplorerCore.NAME); - - public Action OnLogMessage => (object log) => { Logging?.LogMessage(log?.ToString() ?? ""); }; - public Action OnLogWarning => (object log) => { Logging?.LogWarning(log?.ToString() ?? ""); }; - public Action OnLogError => (object log) => { Logging?.LogError(log?.ToString() ?? ""); }; - - // Init - public override void Load() - { - Instance = this; - - ClassInjector.RegisterTypeInIl2Cpp(); - - var obj = new GameObject( - "ExplorerBehaviour", - new Il2CppSystem.Type[] { Il2CppType.Of() } - ); - obj.hideFlags = HideFlags.HideAndDontSave; - GameObject.DontDestroyOnLoad(obj); - - new ExplorerCore(); - } - - // 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) { } - - internal void Awake() - { - Logging.LogMessage("ExplorerBehaviour.Awake"); - } - - internal void Update() - { - ExplorerCore.Update(); - } - } - } -#endif -} -#endif diff --git a/src/Loader/IExplorerLoader.cs b/src/Loader/IExplorerLoader.cs index dc70a5d..9b7b406 100644 --- a/src/Loader/IExplorerLoader.cs +++ b/src/Loader/IExplorerLoader.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using UnityExplorer.Core.Config; namespace UnityExplorer { @@ -10,6 +11,7 @@ namespace UnityExplorer string ExplorerFolder { get; } string ConfigFolder { get; } + IConfigHandler ConfigHandler { get; } Action OnLogMessage { get; } Action OnLogWarning { get; } diff --git a/src/Loader/ExplorerMelonMod.cs b/src/Loader/ML/ExplorerMelonMod.cs similarity index 60% rename from src/Loader/ExplorerMelonMod.cs rename to src/Loader/ML/ExplorerMelonMod.cs index f2c7bd3..5599ef1 100644 --- a/src/Loader/ExplorerMelonMod.cs +++ b/src/Loader/ML/ExplorerMelonMod.cs @@ -3,6 +3,8 @@ using System; using System.IO; using MelonLoader; using UnityExplorer; +using UnityExplorer.Core.Config; +using UnityExplorer.Loader.ML; [assembly: MelonInfo(typeof(ExplorerMelonMod), ExplorerCore.NAME, ExplorerCore.VERSION, ExplorerCore.AUTHOR)] [assembly: MelonGame(null, null)] @@ -16,28 +18,27 @@ namespace UnityExplorer public string ExplorerFolder => Path.Combine("Mods", ExplorerCore.NAME); public string ConfigFolder => ExplorerFolder; - public Action OnLogMessage => (object log) => { MelonLogger.Msg(log?.ToString() ?? ""); }; - public Action OnLogWarning => (object log) => { MelonLogger.Warning(log?.ToString() ?? ""); }; - public Action OnLogError => (object log) => { MelonLogger.Error(log?.ToString() ?? ""); }; + public IConfigHandler ConfigHandler => _configHandler; + public MelonLoaderConfigHandler _configHandler; + + public Action OnLogMessage => MelonLogger.Msg; + public Action OnLogWarning => MelonLogger.Warning; + public Action OnLogError => MelonLogger.Error; public Harmony.HarmonyInstance HarmonyInstance => Instance.Harmony; public override void OnApplicationStart() { Instance = this; + _configHandler = new MelonLoaderConfigHandler(); - new ExplorerCore(); + ExplorerCore.Init(this); } public override void OnUpdate() { ExplorerCore.Update(); } - - //public override void OnSceneWasLoaded(int buildIndex, string sceneName) - //{ - // ExplorerCore.Instance.OnSceneLoaded(); - //} } } #endif \ No newline at end of file diff --git a/src/Loader/ML/MelonLoaderConfigHandler.cs b/src/Loader/ML/MelonLoaderConfigHandler.cs new file mode 100644 index 0000000..d00a81a --- /dev/null +++ b/src/Loader/ML/MelonLoaderConfigHandler.cs @@ -0,0 +1,99 @@ +#if ML +using MelonLoader; +using MelonLoader.Tomlyn.Model; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityExplorer.Core.Config; + +namespace UnityExplorer.Loader.ML +{ + public class MelonLoaderConfigHandler : IConfigHandler + { + internal const string CTG_NAME = "UnityExplorer"; + + internal MelonPreferences_Category prefCategory; + + public void Init() + { + prefCategory = MelonPreferences.CreateCategory(CTG_NAME, $"{CTG_NAME} Settings"); + + MelonPreferences.Mapper.RegisterMapper(KeycodeReader, KeycodeWriter); + } + + public void LoadConfig() + { + foreach (var entry in ConfigManager.ConfigElements) + { + var key = entry.Key; + if (prefCategory.GetEntry(key) is MelonPreferences_Entry) + { + var config = entry.Value; + config.BoxedValue = config.GetLoaderConfigValue(); + } + } + } + + public void RegisterConfigElement(ConfigElement config) + { + var entry = prefCategory.CreateEntry(config.Name, config.Value, null, config.IsInternal) as MelonPreferences_Entry; + + entry.OnValueChangedUntyped += () => + { + if ((entry.Value == null && config.Value == null) || config.Value.Equals(entry.Value)) + return; + + config.Value = entry.Value; + }; + } + + public void SetConfigValue(ConfigElement config, T value) + { + if (prefCategory.GetEntry(config.Name) is MelonPreferences_Entry entry) + { + entry.Value = value; + entry.Save(); + MelonPreferences.Save(); + } + } + + public T GetConfigValue(ConfigElement config) + { + if (prefCategory.GetEntry(config.Name) is MelonPreferences_Entry entry) + return entry.Value; + + return default; + } + + public void SaveConfig() + { + // Not necessary + } + + public static KeyCode KeycodeReader(TomlObject value) + { + try + { + KeyCode kc = (KeyCode)Enum.Parse(typeof(KeyCode), (value as TomlString).Value); + + if (kc == default) + throw new Exception(); + + return kc; + } + catch + { + return KeyCode.F7; + } + } + + public static TomlObject KeycodeWriter(KeyCode value) + { + return MelonPreferences.Mapper.ToToml(value.ToString()); + } + } +} + +#endif \ No newline at end of file diff --git a/src/Loader/ExplorerStandalone.cs b/src/Loader/STANDALONE/ExplorerStandalone.cs similarity index 74% rename from src/Loader/ExplorerStandalone.cs rename to src/Loader/STANDALONE/ExplorerStandalone.cs index 9ad1f56..48a495e 100644 --- a/src/Loader/ExplorerStandalone.cs +++ b/src/Loader/STANDALONE/ExplorerStandalone.cs @@ -4,6 +4,8 @@ using System; using System.IO; using System.Reflection; using UnityEngine; +using UnityExplorer.Core.Config; +using UnityExplorer.Loader.STANDALONE; #if CPP using UnhollowerRuntimeLib; #endif @@ -13,20 +15,27 @@ namespace UnityExplorer public class ExplorerStandalone : IExplorerLoader { /// - /// Call this to initialize UnityExplorer. Optionally, also subscribe to the 'OnLog' event to handle logging. + /// Call this to initialize UnityExplorer without adding a log listener. /// /// The new (or active, if one exists) instance of ExplorerStandalone. public static ExplorerStandalone CreateInstance() + => CreateInstance(null); + + /// + /// Call this to initialize UnityExplorer and add a listener for UnityExplorer's log messages. + /// + /// Your log listener to handle UnityExplorer logs. + /// The new (or active, if one exists) instance of ExplorerStandalone. + public static ExplorerStandalone CreateInstance(Action logListener) { if (Instance != null) return Instance; - return new ExplorerStandalone(); - } + OnLog += logListener; - private ExplorerStandalone() - { - Init(); + var instance = new ExplorerStandalone(); + instance.Init(); + return instance; } public static ExplorerStandalone Instance { get; private set; } @@ -39,6 +48,9 @@ namespace UnityExplorer public Harmony HarmonyInstance => s_harmony; public static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID); + public IConfigHandler ConfigHandler => _configHandler; + private IConfigHandler _configHandler; + public string ExplorerFolder { get @@ -70,6 +82,8 @@ namespace UnityExplorer private void Init() { Instance = this; + _configHandler = new StandaloneConfigHandler(); + #if CPP ClassInjector.RegisterTypeInIl2Cpp(); @@ -81,13 +95,13 @@ namespace UnityExplorer var obj = new GameObject( "ExplorerBehaviour", new Type[] { typeof(ExplorerBehaviour) } - ); + ); #endif - obj.hideFlags = HideFlags.HideAndDontSave; GameObject.DontDestroyOnLoad(obj); + obj.hideFlags = HideFlags.HideAndDontSave; - new ExplorerCore(); + ExplorerCore.Init(this); } public class ExplorerBehaviour : MonoBehaviour diff --git a/src/Loader/STANDALONE/StandaloneConfigHandler.cs b/src/Loader/STANDALONE/StandaloneConfigHandler.cs new file mode 100644 index 0000000..5c9159d --- /dev/null +++ b/src/Loader/STANDALONE/StandaloneConfigHandler.cs @@ -0,0 +1,103 @@ +#if STANDALONE +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityExplorer.Core.Config; +using IniParser.Parser; +using System.IO; +using UnityEngine; + +namespace UnityExplorer.Loader.STANDALONE +{ + public class StandaloneConfigHandler : IConfigHandler + { + internal static IniDataParser _parser; + internal static string INI_PATH; + + public void Init() + { + INI_PATH = Path.Combine(ExplorerCore.Loader.ConfigFolder, "config.ini"); + _parser = new IniDataParser(); + _parser.Configuration.CommentString = "#"; + } + + public void LoadConfig() + { + if (!TryLoadConfig()) + SaveConfig(); + } + + public void RegisterConfigElement(ConfigElement element) + { + // Not necessary + } + + public void SetConfigValue(ConfigElement element, T value) + { + // Not necessary, just save. + SaveConfig(); + } + + public T GetConfigValue(ConfigElement element) + { + // Not necessary, just return the value. + return element.Value; + } + + public bool TryLoadConfig() + { + try + { + if (!File.Exists(INI_PATH)) + return false; + + string ini = File.ReadAllText(INI_PATH); + + var data = _parser.Parse(ini); + + foreach (var config in data.Sections["Config"]) + { + if (ConfigManager.ConfigElements.TryGetValue(config.KeyName, out IConfigElement configElement)) + configElement.BoxedValue = StringToConfigValue(config.Value, configElement.ElementType); + } + + return true; + } + catch + { + return false; + } + } + + public object StringToConfigValue(string value, Type elementType) + { + if (elementType == typeof(KeyCode)) + return (KeyCode)Enum.Parse(typeof(KeyCode), value); + else if (elementType == typeof(bool)) + return bool.Parse(value); + else if (elementType == typeof(int)) + return int.Parse(value); + else + return value; + } + + public void SaveConfig() + { + var data = new IniParser.Model.IniData(); + + data.Sections.AddSection("Config"); + var sec = data.Sections["Config"]; + + foreach (var entry in ConfigManager.ConfigElements) + sec.AddKey(entry.Key, entry.Value.BoxedValue.ToString()); + + if (!Directory.Exists(ExplorerCore.Loader.ConfigFolder)) + Directory.CreateDirectory(ExplorerCore.Loader.ConfigFolder); + + File.WriteAllText(INI_PATH, data.ToString()); + } + } +} + +#endif \ No newline at end of file diff --git a/src/UI/CacheObject/CacheConfigEntry.cs b/src/UI/CacheObject/CacheConfigEntry.cs new file mode 100644 index 0000000..518daf6 --- /dev/null +++ b/src/UI/CacheObject/CacheConfigEntry.cs @@ -0,0 +1,94 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using UnityEngine.UI; +using UnityExplorer.Core.Config; +using UnityExplorer.UI.InteractiveValues; + +namespace UnityExplorer.UI.CacheObject +{ + public class CacheConfigEntry : CacheObjectBase + { + public IConfigElement RefConfig { get; } + + public override Type FallbackType => RefConfig.ElementType; + + public override bool HasEvaluated => true; + public override bool HasParameters => false; + public override bool IsMember => false; + public override bool CanWrite => true; + + public CacheConfigEntry(IConfigElement config, GameObject parent) + { + RefConfig = config; + + m_parentContent = parent; + + config.OnValueChangedNotify += () => { UpdateValue(); }; + + CreateIValue(config.BoxedValue, config.ElementType); + } + + public override void CreateIValue(object value, Type fallbackType) + { + IValue = InteractiveValue.Create(value, fallbackType); + IValue.Owner = this; + IValue.m_mainContentParent = m_rightGroup; + IValue.m_subContentParent = this.m_subContent; + } + + public override void UpdateValue() + { + IValue.Value = RefConfig.BoxedValue; + + base.UpdateValue(); + } + + public override void SetValue() + { + RefConfig.BoxedValue = IValue.Value; + ConfigManager.Handler.SaveConfig(); + } + + internal GameObject m_leftGroup; + internal GameObject m_rightGroup; + + internal override void ConstructUI() + { + base.ConstructUI(); + + var horiGroup = UIFactory.CreateHorizontalGroup(m_mainContent, "ConfigEntryHolder", true, false, true, true, 5, new Vector4(2,2,2,2)); + UIFactory.SetLayoutElement(horiGroup, minHeight: 30, flexibleHeight: 0); + + // left group + + m_leftGroup = UIFactory.CreateHorizontalGroup(horiGroup, "ConfigTitleGroup", false, false, true, true, 4, default, new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(m_leftGroup, minHeight: 25, flexibleHeight: 0, minWidth: 125, flexibleWidth: 200); + + // config entry label + + var configLabel = UIFactory.CreateLabel(m_leftGroup, "ConfigLabel", this.RefConfig.Name, TextAnchor.MiddleLeft); + var leftRect = configLabel.GetComponent(); + leftRect.anchorMin = Vector2.zero; + leftRect.anchorMax = Vector2.one; + leftRect.offsetMin = Vector2.zero; + leftRect.offsetMax = Vector2.zero; + leftRect.sizeDelta = Vector2.zero; + UIFactory.SetLayoutElement(configLabel.gameObject, minWidth: 250, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0); + + // right group + + m_rightGroup = UIFactory.CreateVerticalGroup(horiGroup, "ConfigValueGroup", false, false, true, true, 2, new Vector4(4,2,0,0), + new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(m_rightGroup, minHeight: 25, minWidth: 150, flexibleHeight: 0, flexibleWidth: 5000); + + if (IValue != null) + { + IValue.m_mainContentParent = m_rightGroup; + IValue.m_subContentParent = this.m_subContent; + } + } + } +} diff --git a/src/Core/Inspectors/Reflection/CacheObject/CacheEnumerated.cs b/src/UI/CacheObject/CacheEnumerated.cs similarity index 70% rename from src/Core/Inspectors/Reflection/CacheObject/CacheEnumerated.cs rename to src/UI/CacheObject/CacheEnumerated.cs index 4ae12ba..97fae07 100644 --- a/src/Core/Inspectors/Reflection/CacheObject/CacheEnumerated.cs +++ b/src/UI/CacheObject/CacheEnumerated.cs @@ -6,8 +6,9 @@ using System.Text; using UnityExplorer.UI; using UnityEngine; using UnityEngine.UI; +using UnityExplorer.UI.InteractiveValues; -namespace UnityExplorer.Core.Inspectors.Reflection +namespace UnityExplorer.UI.CacheObject { public class CacheEnumerated : CacheObjectBase { @@ -44,18 +45,11 @@ namespace UnityExplorer.Core.Inspectors.Reflection { base.ConstructUI(); - var rowObj = UIFactory.CreateHorizontalGroup(m_mainContent, new Color(1, 1, 1, 0)); - var rowGroup = rowObj.GetComponent(); - rowGroup.padding.left = 5; - rowGroup.padding.right = 2; + var rowObj = UIFactory.CreateHorizontalGroup(m_mainContent, "CacheEnumeratedGroup", false, true, true, true, 0, new Vector4(0,0,5,2), + new Color(1, 1, 1, 0)); - var indexLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft); - var indexLayout = indexLabelObj.AddComponent(); - indexLayout.minWidth = 20; - indexLayout.flexibleWidth = 30; - indexLayout.minHeight = 25; - var indexText = indexLabelObj.GetComponent(); - indexText.text = this.Index + ":"; + var indexLabel = UIFactory.CreateLabel(rowObj, "IndexLabel", $"{this.Index}:", TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(indexLabel.gameObject, minWidth: 20, flexibleWidth: 30, minHeight: 25); IValue.m_mainContentParent = rowObj; } diff --git a/src/Core/Inspectors/Reflection/CacheObject/CacheField.cs b/src/UI/CacheObject/CacheField.cs similarity index 95% rename from src/Core/Inspectors/Reflection/CacheObject/CacheField.cs rename to src/UI/CacheObject/CacheField.cs index c507927..d3f5160 100644 --- a/src/Core/Inspectors/Reflection/CacheObject/CacheField.cs +++ b/src/UI/CacheObject/CacheField.cs @@ -6,7 +6,7 @@ using System.Reflection; using UnityExplorer.UI; using UnityEngine; -namespace UnityExplorer.Core.Inspectors.Reflection +namespace UnityExplorer.UI.CacheObject { public class CacheField : CacheMember { diff --git a/src/Core/Inspectors/Reflection/CacheObject/CacheMember.cs b/src/UI/CacheObject/CacheMember.cs similarity index 64% rename from src/Core/Inspectors/Reflection/CacheObject/CacheMember.cs rename to src/UI/CacheObject/CacheMember.cs index 3039648..86144d8 100644 --- a/src/Core/Inspectors/Reflection/CacheObject/CacheMember.cs +++ b/src/UI/CacheObject/CacheMember.cs @@ -5,13 +5,14 @@ using System.Reflection; using UnityEngine; using UnityEngine.UI; using UnityExplorer.UI; -using UnityExplorer.UI.Reusable; using UnityExplorer.Core.Unity; using UnityExplorer.Core.Runtime; using UnityExplorer.Core; using UnityExplorer.UI.Utility; +using UnityExplorer.UI.InteractiveValues; +using UnityExplorer.UI.Main.Home.Inspectors.Reflection; -namespace UnityExplorer.Core.Inspectors.Reflection +namespace UnityExplorer.UI.CacheObject { public abstract class CacheMember : CacheObjectBase { @@ -214,75 +215,46 @@ namespace UnityExplorer.Core.Inspectors.Reflection { base.ConstructUI(); - var topGroupObj = UIFactory.CreateHorizontalGroup(m_mainContent, new Color(1, 1, 1, 0)); + var topGroupObj = UIFactory.CreateHorizontalGroup(m_mainContent, "CacheMemberGroup", false, false, true, true, 10, new Vector4(0, 0, 3, 3), + new Color(1, 1, 1, 0)); + m_topRowRect = topGroupObj.GetComponent(); - var topLayout = topGroupObj.AddComponent(); - topLayout.minHeight = 25; - topLayout.flexibleHeight = 0; - topLayout.minWidth = 300; - topLayout.flexibleWidth = 5000; - var topGroup = topGroupObj.GetComponent(); - topGroup.childForceExpandHeight = false; - topGroup.childForceExpandWidth = false; - topGroup.SetChildControlHeight(true); - topGroup.SetChildControlWidth(true); - topGroup.spacing = 10; - topGroup.padding.left = 3; - topGroup.padding.right = 3; - topGroup.padding.top = 0; - topGroup.padding.bottom = 0; + + UIFactory.SetLayoutElement(topGroupObj, minHeight: 25, flexibleHeight: 0, minWidth: 300, flexibleWidth: 5000); // left group - m_leftGroup = UIFactory.CreateHorizontalGroup(topGroupObj, new Color(1, 1, 1, 0)); - var leftLayout = m_leftGroup.AddComponent(); - leftLayout.minHeight = 25; - leftLayout.flexibleHeight = 0; - leftLayout.minWidth = 125; - leftLayout.flexibleWidth = 200; - var leftGroup = m_leftGroup.GetComponent(); - leftGroup.childForceExpandHeight = true; - leftGroup.childForceExpandWidth = false; - leftGroup.SetChildControlHeight(true); - leftGroup.SetChildControlWidth(true); - leftGroup.spacing = 4; + m_leftGroup = UIFactory.CreateHorizontalGroup(topGroupObj, "LeftGroup", false, true, true, true, 4, default, new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(m_leftGroup, minHeight: 25, flexibleHeight: 0, minWidth: 125, flexibleWidth: 200); // member label - var labelObj = UIFactory.CreateLabel(m_leftGroup, TextAnchor.MiddleLeft); - var leftRect = labelObj.GetComponent(); + m_memLabelText = UIFactory.CreateLabel(m_leftGroup, "MemLabelText", RichTextName, TextAnchor.MiddleLeft); + m_memLabelText.horizontalOverflow = HorizontalWrapMode.Wrap; + var leftRect = m_memLabelText.GetComponent(); leftRect.anchorMin = Vector2.zero; leftRect.anchorMax = Vector2.one; leftRect.offsetMin = Vector2.zero; leftRect.offsetMax = Vector2.zero; leftRect.sizeDelta = Vector2.zero; - m_leftLayout = labelObj.AddComponent(); + m_leftLayout = m_memLabelText.gameObject.AddComponent(); m_leftLayout.preferredWidth = 125; m_leftLayout.minHeight = 25; m_leftLayout.flexibleHeight = 100; - var labelFitter = labelObj.AddComponent(); + var labelFitter = m_memLabelText.gameObject.AddComponent(); labelFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; labelFitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize; - m_memLabelText = labelObj.GetComponent(); - m_memLabelText.horizontalOverflow = HorizontalWrapMode.Wrap; - m_memLabelText.text = this.RichTextName; // right group - m_rightGroup = UIFactory.CreateVerticalGroup(topGroupObj, new Color(1, 1, 1, 0)); + m_rightGroup = UIFactory.CreateVerticalGroup(topGroupObj, "RightGroup", false, true, true, true, 2, new Vector4(4,2,0,0), + new Color(1, 1, 1, 0)); + m_rightLayout = m_rightGroup.AddComponent(); m_rightLayout.minHeight = 25; m_rightLayout.flexibleHeight = 480; m_rightLayout.minWidth = 125; m_rightLayout.flexibleWidth = 5000; - var rightGroup = m_rightGroup.GetComponent(); - rightGroup.childForceExpandHeight = true; - rightGroup.childForceExpandWidth = false; - rightGroup.SetChildControlHeight(true); - rightGroup.SetChildControlWidth(true); - rightGroup.spacing = 2; - rightGroup.padding.top = 4; - rightGroup.padding.bottom = 2; ConstructArgInput(out GameObject argsHolder); @@ -297,27 +269,17 @@ namespace UnityExplorer.Core.Inspectors.Reflection if (HasParameters) { - argsHolder = UIFactory.CreateVerticalGroup(m_rightGroup, new Color(1, 1, 1, 0)); - var argsGroup = argsHolder.GetComponent(); - argsGroup.spacing = 4; + argsHolder = UIFactory.CreateVerticalGroup(m_rightGroup, "ArgsHolder", true, false, true, true, 4, new Color(1, 1, 1, 0)); if (this is CacheMethod cm && cm.GenericArgs.Length > 0) - { cm.ConstructGenericArgInput(argsHolder); - } - - // todo normal args if (m_arguments.Length > 0) { - var titleObj = UIFactory.CreateLabel(argsHolder, TextAnchor.MiddleLeft); - var titleText = titleObj.GetComponent(); - titleText.text = "Arguments:"; + UIFactory.CreateLabel(argsHolder, "ArgumentsLabel", "Arguments:", TextAnchor.MiddleLeft); for (int i = 0; i < m_arguments.Length; i++) - { AddArgRow(i, argsHolder); - } } argsHolder.SetActive(false); @@ -328,30 +290,16 @@ namespace UnityExplorer.Core.Inspectors.Reflection { var arg = m_arguments[i]; - var rowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0)); - var rowLayout = rowObj.AddComponent(); - rowLayout.minHeight = 25; - rowLayout.flexibleWidth = 5000; - var rowGroup = rowObj.GetComponent(); - rowGroup.childForceExpandHeight = false; - rowGroup.childForceExpandWidth = true; - rowGroup.spacing = 4; + var rowObj = UIFactory.CreateHorizontalGroup(parent, "ArgRow", true, false, true, true, 4, default, new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 5000); - var argLabelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft); - var argLabelLayout = argLabelObj.AddComponent(); - argLabelLayout.minHeight = 25; - var argText = argLabelObj.GetComponent(); var argTypeTxt = SignatureHighlighter.ParseFullSyntax(arg.ParameterType, false); - argText.text = $"{argTypeTxt} {arg.Name}"; + var argLabel = UIFactory.CreateLabel(rowObj, "ArgLabel", $"{argTypeTxt} {arg.Name}", + TextAnchor.MiddleLeft); + UIFactory.SetLayoutElement(argLabel.gameObject, minHeight: 25); - var argInputObj = UIFactory.CreateInputField(rowObj, 14, (int)TextAnchor.MiddleLeft, 1); - var argInputLayout = argInputObj.AddComponent(); - argInputLayout.flexibleWidth = 1200; - argInputLayout.preferredWidth = 150; - argInputLayout.minWidth = 20; - argInputLayout.minHeight = 25; - argInputLayout.flexibleHeight = 0; - //argInputLayout.layoutPriority = 2; + var argInputObj = UIFactory.CreateInputField(rowObj, "ArgInput", "...", 14, (int)TextAnchor.MiddleLeft, 1); + UIFactory.SetLayoutElement(argInputObj, flexibleWidth: 1200, preferredWidth: 150, minWidth: 20, minHeight: 25, flexibleHeight: 0); var argInput = argInputObj.GetComponent(); argInput.onValueChanged.AddListener((string val) => { m_argumentInput[i] = val; }); @@ -367,38 +315,29 @@ namespace UnityExplorer.Core.Inspectors.Reflection { if (HasParameters) { - var evalGroupObj = UIFactory.CreateHorizontalGroup(m_rightGroup, new Color(1, 1, 1, 0)); - var evalGroup = evalGroupObj.GetComponent(); - evalGroup.childForceExpandWidth = false; - evalGroup.childForceExpandHeight = false; - evalGroup.spacing = 5; - var evalGroupLayout = evalGroupObj.AddComponent(); - evalGroupLayout.minHeight = 25; - evalGroupLayout.flexibleHeight = 0; - evalGroupLayout.flexibleWidth = 5000; + var evalGroupObj = UIFactory.CreateHorizontalGroup(m_rightGroup, "EvalGroup", false, false, true, true, 5, + default, new Color(1, 1, 1, 0)); + UIFactory.SetLayoutElement(evalGroupObj, minHeight: 25, flexibleHeight: 0, flexibleWidth: 5000); - var evalButtonObj = UIFactory.CreateButton(evalGroupObj, new Color(0.4f, 0.4f, 0.4f)); - var evalLayout = evalButtonObj.AddComponent(); - evalLayout.minWidth = 100; - evalLayout.minHeight = 22; - evalLayout.flexibleWidth = 0; - var evalText = evalButtonObj.GetComponentInChildren(); - evalText.text = $"Evaluate ({ParamCount})"; - - var evalButton = evalButtonObj.GetComponent