diff --git a/src/CachedObjects/CacheObjectBase.cs b/src/CachedObjects/CacheObjectBase.cs index f083b64..763918d 100644 --- a/src/CachedObjects/CacheObjectBase.cs +++ b/src/CachedObjects/CacheObjectBase.cs @@ -121,11 +121,6 @@ namespace Explorer return null; } - // This is pretty ugly, could probably make a cleaner implementation. - // However, the only cleaner ways I can think of are slower and probably not worth it. - - // Note: the order is somewhat important. - if (mi != null) { holder = new CacheMethod(); @@ -140,7 +135,14 @@ namespace Explorer } else if (valueType.IsEnum) { - holder = new CacheEnum(); + if (valueType.GetCustomAttributes(typeof(FlagsAttribute), false) is object[] attributes && attributes.Length > 0) + { + holder = new CacheEnumFlags(); + } + else + { + holder = new CacheEnum(); + } } else if (valueType == typeof(Vector2) || valueType == typeof(Vector3) || valueType == typeof(Vector4)) { diff --git a/src/CachedObjects/Struct/CacheEnum.cs b/src/CachedObjects/Struct/CacheEnum.cs index 45933e7..0036a77 100644 --- a/src/CachedObjects/Struct/CacheEnum.cs +++ b/src/CachedObjects/Struct/CacheEnum.cs @@ -23,7 +23,18 @@ namespace Explorer if (ValueType != null) { - EnumNames = Enum.GetNames(ValueType); + // using GetValues not GetNames, to catch instances of weird enums (eg CameraClearFlags) + var values = Enum.GetValues(ValueType); + + var list = new List(); + foreach (var value in values) + { + var v = value.ToString(); + if (list.Contains(v)) continue; + list.Add(v); + } + + EnumNames = list.ToArray(); } else { @@ -37,12 +48,12 @@ namespace Explorer { if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(25) })) { - SetEnum(ref Value, -1); + SetEnum(-1); SetValue(); } if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(25) })) { - SetEnum(ref Value, 1); + SetEnum(1); SetValue(); } } @@ -50,15 +61,15 @@ namespace Explorer GUILayout.Label(Value.ToString() + " (" + ValueType + ")", null); } - public void SetEnum(ref object value, int change) + public void SetEnum(int change) { var names = EnumNames.ToList(); - int newindex = names.IndexOf(value.ToString()) + change; + int newindex = names.IndexOf(Value.ToString()) + change; - if ((change < 0 && newindex >= 0) || (change > 0 && newindex < names.Count)) + if (newindex >= 0 && newindex < names.Count) { - value = Enum.Parse(ValueType, names[newindex]); + Value = Enum.Parse(ValueType, EnumNames[newindex]); } } } diff --git a/src/CachedObjects/Struct/CacheEnumFlags.cs b/src/CachedObjects/Struct/CacheEnumFlags.cs new file mode 100644 index 0000000..c9e7fe2 --- /dev/null +++ b/src/CachedObjects/Struct/CacheEnumFlags.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using MelonLoader; +using UnityEngine; + +namespace Explorer +{ + public class CacheEnumFlags : CacheObjectBase, IExpandHeight + { + public string[] EnumNames = new string[0]; + public bool[] m_enabledFlags = new bool[0]; + + public bool IsExpanded { get; set; } + public float WhiteSpace { get; set; } = 215f; + + public override void Init() + { + base.Init(); + + if (ValueType == null && Value != null) + { + ValueType = Value.GetType(); + } + + if (ValueType != null) + { + EnumNames = Enum.GetNames(ValueType); + + m_enabledFlags = new bool[EnumNames.Length]; + + UpdateValue(); + } + else + { + ReflectionException = "Unknown, could not get Enum names."; + } + } + + public void SetFlagsFromInput() + { + string val = ""; + for (int i = 0; i < EnumNames.Length; i++) + { + if (m_enabledFlags[i]) + { + if (val != "") val += ", "; + val += EnumNames[i]; + } + } + Value = Enum.Parse(ValueType, val); + SetValue(); + } + + public override void UpdateValue() + { + base.UpdateValue(); + + try + { + var enabledNames = Value.ToString().Split(',').Select(it => it.Trim()); + + for (int i = 0; i < EnumNames.Length; i++) + { + m_enabledFlags[i] = enabledNames.Contains(EnumNames[i]); + } + } + catch (Exception e) + { + MelonLogger.Log(e.ToString()); + } + } + + + public override void DrawValue(Rect window, float width) + { + if (CanWrite) + { + if (!IsExpanded) + { + if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = true; + } + } + else + { + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) + { + IsExpanded = false; + } + } + } + + GUILayout.Label(Value.ToString() + " (" + ValueType + ")", null); + + if (IsExpanded) + { + GUILayout.EndHorizontal(); + + var whitespace = CalcWhitespace(window); + + for (int i = 0; i < EnumNames.Length; i++) + { + GUILayout.BeginHorizontal(null); + GUIUnstrip.Space(whitespace); + + m_enabledFlags[i] = GUILayout.Toggle(m_enabledFlags[i], EnumNames[i], null); + + GUILayout.EndHorizontal(); + } + + GUILayout.BeginHorizontal(null); + GUIUnstrip.Space(whitespace); + if (GUILayout.Button("Apply", new GUILayoutOption[] { GUILayout.Width(155) })) + { + SetFlagsFromInput(); + } + GUILayout.EndHorizontal(); + + GUILayout.BeginHorizontal(null); + } + } + } +} diff --git a/src/CachedObjects/Struct/CachePrimitive.cs b/src/CachedObjects/Struct/CachePrimitive.cs index 6c55a19..ded5257 100644 --- a/src/CachedObjects/Struct/CachePrimitive.cs +++ b/src/CachedObjects/Struct/CachePrimitive.cs @@ -1,7 +1,9 @@ using System; +using System.Collections; using System.Collections.Generic; using System.Reflection; using MelonLoader; +using UnhollowerRuntimeLib; using UnityEngine; namespace Explorer @@ -16,6 +18,12 @@ namespace Explorer public MethodInfo ParseMethod => m_parseMethod ?? (m_parseMethod = Value.GetType().GetMethod("Parse", new Type[] { typeof(string) })); private MethodInfo m_parseMethod; + private bool m_canBitwiseOperate; + private bool m_inBitwiseMode; + private string m_bitwiseOperatorInput = "0"; + private string m_bitwiseToString; + //private BitArray m_bitMask; // not needed I think + public override void Init() { if (ValueType == null) @@ -37,17 +45,31 @@ namespace Explorer { m_isBool = true; } + + m_canBitwiseOperate = typeof(int).IsAssignableFrom(ValueType); } public override void UpdateValue() { base.UpdateValue(); + RefreshToString(); + } + + public void RefreshToString() + { m_valueToString = Value?.ToString(); + + if (m_inBitwiseMode) + { + var _int = (int)Value; + m_bitwiseToString = Convert.ToString(_int, toBase: 2); + } } public override void DrawValue(Rect window, float width) { + // bool uses Toggle if (m_isBool) { var b = (bool)Value; @@ -65,35 +87,127 @@ namespace Explorer { GUILayout.Label(label, null); } + + return; + } + + // all other non-bool values use TextField + + GUILayout.BeginVertical(null); + + GUILayout.BeginHorizontal(null); + + // using ValueType.Name instead of ValueTypeName, because we only want the short name. + GUILayout.Label("" + ValueType.Name + "", new GUILayoutOption[] { GUILayout.Width(50) }); + + int dynSize = 25 + (m_valueToString.Length * 15); + var maxwidth = window.width - 310f; + if (CanWrite) maxwidth -= 60; + + if (dynSize > maxwidth) + { + m_valueToString = GUILayout.TextArea(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(maxwidth) }); } else { - // using ValueType.Name instead of ValueTypeName, because we only want the short name. - GUILayout.Label("" + ValueType.Name + "", new GUILayoutOption[] { GUILayout.Width(50) }); + m_valueToString = GUILayout.TextField(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(dynSize) }); + } - int dynSize = 25 + (m_valueToString.Length * 15); - var maxwidth = window.width - 310f; - if (CanWrite) maxwidth -= 60; - - if (dynSize > maxwidth) + if (CanWrite) + { + if (GUILayout.Button("Apply", new GUILayoutOption[] { GUILayout.Width(60) })) { - m_valueToString = GUILayout.TextArea(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(maxwidth) }); - } - else - { - m_valueToString = GUILayout.TextField(m_valueToString, new GUILayoutOption[] { GUILayout.MaxWidth(dynSize) }); + SetValueFromInput(m_valueToString); + RefreshToString(); } + } + if (m_canBitwiseOperate) + { + bool orig = m_inBitwiseMode; + m_inBitwiseMode = GUILayout.Toggle(m_inBitwiseMode, "Bitwise?", null); + if (orig != m_inBitwiseMode) + { + RefreshToString(); + } + } + + GUIUnstrip.Space(10); + + GUILayout.EndHorizontal(); + + if (m_inBitwiseMode) + { if (CanWrite) { - if (GUILayout.Button("Apply", new GUILayoutOption[] { GUILayout.Width(60) })) + GUILayout.BeginHorizontal(null); + + GUI.skin.label.alignment = TextAnchor.MiddleRight; + GUILayout.Label("RHS:", new GUILayoutOption[] { GUILayout.Width(35) }); + GUI.skin.label.alignment = TextAnchor.UpperLeft; + + if (GUILayout.Button("~", new GUILayoutOption[] { GUILayout.Width(25) })) { - SetValueFromInput(m_valueToString); + if (int.TryParse(m_bitwiseOperatorInput, out int bit)) + { + Value = ~bit; + RefreshToString(); + } } + + if (GUILayout.Button("<<", new GUILayoutOption[] { GUILayout.Width(25) })) + { + if (int.TryParse(m_bitwiseOperatorInput, out int bit)) + { + Value = (int)Value << bit; + RefreshToString(); + } + } + if (GUILayout.Button(">>", new GUILayoutOption[] { GUILayout.Width(25) })) + { + if (int.TryParse(m_bitwiseOperatorInput, out int bit)) + { + Value = (int)Value >> bit; + RefreshToString(); + } + } + if (GUILayout.Button("|", new GUILayoutOption[] { GUILayout.Width(25) })) + { + if (int.TryParse(m_bitwiseOperatorInput, out int bit)) + { + Value = (int)Value | bit; + RefreshToString(); + } + } + if (GUILayout.Button("&", new GUILayoutOption[] { GUILayout.Width(25) })) + { + if (int.TryParse(m_bitwiseOperatorInput, out int bit)) + { + Value = (int)Value & bit; + RefreshToString(); + } + } + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) + { + if (int.TryParse(m_bitwiseOperatorInput, out int bit)) + { + Value = (int)Value ^ bit; + RefreshToString(); + } + } + + m_bitwiseOperatorInput = GUILayout.TextField(m_bitwiseOperatorInput, new GUILayoutOption[] { GUILayout.Width(55) }); + + GUILayout.EndHorizontal(); } - GUIUnstrip.Space(10); + GUILayout.BeginHorizontal(null); + GUILayout.Label($"Binary:", new GUILayoutOption[] { GUILayout.Width(60) }); + GUILayout.TextField(m_bitwiseToString, null); + GUILayout.EndHorizontal(); } + + GUILayout.EndVertical(); } public void SetValueFromInput(string valueString) @@ -113,6 +227,16 @@ namespace Explorer try { Value = ParseMethod.Invoke(null, new object[] { valueString }); + + //if (m_inBitwiseMode) + //{ + // var method = typeof(Convert).GetMethod($"To{ValueType.Name}", new Type[] { typeof(string), typeof(int) }); + // Value = method.Invoke(null, new object[] { valueString, 2 }); + //} + //else + //{ + // Value = ParseMethod.Invoke(null, new object[] { valueString }); + //} } catch (Exception e) { diff --git a/src/CppExplorer.cs b/src/CppExplorer.cs index d5cac1f..19b90e9 100644 --- a/src/CppExplorer.cs +++ b/src/CppExplorer.cs @@ -6,7 +6,7 @@ namespace Explorer public class CppExplorer : MelonMod { public const string NAME = "CppExplorer"; - public const string VERSION = "1.7.4"; + public const string VERSION = "1.7.5"; public const string AUTHOR = "Sinai"; public const string GUID = "com.sinai.cppexplorer"; diff --git a/src/CppExplorer.csproj b/src/CppExplorer.csproj index a885d57..892c8e2 100644 --- a/src/CppExplorer.csproj +++ b/src/CppExplorer.csproj @@ -83,6 +83,7 @@ + diff --git a/src/Menu/Windows/GameObjectWindow.cs b/src/Menu/Windows/GameObjectWindow.cs index 5370dc0..8d07c88 100644 --- a/src/Menu/Windows/GameObjectWindow.cs +++ b/src/Menu/Windows/GameObjectWindow.cs @@ -568,7 +568,7 @@ namespace Explorer GUILayout.EndHorizontal(); bool b = m_localContext; - b = GUILayout.Toggle(b, "Use local transform values?", null); + b = GUILayout.Toggle(b, "Use local transform values?", null); if (b != m_localContext) { m_localContext = b; @@ -609,7 +609,7 @@ namespace Explorer private void BoolToggle(ref bool value, string message) { string lbl = "{message}"; value = GUILayout.Toggle(value, lbl, null); diff --git a/src/Tests/TestClass.cs b/src/Tests/TestClass.cs index e06e693..92cccb2 100644 --- a/src/Tests/TestClass.cs +++ b/src/Tests/TestClass.cs @@ -2,6 +2,9 @@ using System.Collections.Generic; using System; using UnityEngine; +using System.Reflection; +using System.Collections.Specialized; +using MelonLoader; // used to test multiple generic constraints public class TestGeneric : IComparable @@ -11,10 +14,33 @@ public class TestGeneric : IComparable public int CompareTo(string other) => throw new NotImplementedException(); } +[Flags] +public enum TestFlags +{ + Red, + Green, + Blue +} + +// test non-flags weird enum +public enum WeirdEnum +{ + First = 1, + Second, + Third = 2, + Fourth, + Fifth +} + namespace Explorer.Tests { public class TestClass { + public static TestFlags testFlags = TestFlags.Blue | TestFlags.Green; + public static WeirdEnum testWeird = WeirdEnum.First; + + public static int testBitmask; + public static TestClass Instance => m_instance ?? (m_instance = new TestClass()); private static TestClass m_instance; @@ -24,13 +50,14 @@ namespace Explorer.Tests ILHashSetTest.Add("1"); ILHashSetTest.Add("2"); ILHashSetTest.Add("3"); + + testBitmask = 1 | 2; } public static int StaticProperty => 5; public static int StaticField = 5; public int NonStaticField; - public static string TestGeneric(string arg0) where C : Component where T : TestGeneric, IComparable { return $"C: '{typeof(C).FullName}', T: '{typeof(T).FullName}', arg0: '{arg0}'";