Compare commits

...

6 Commits
2.0.1 ... 2.0.4

Author SHA1 Message Date
39d9585f1d 2.0.4
* Added ability to see and change the layer of a gameobject from the GameObject inspector more easily, and shows you the actual layer name (where possible).
* Fixed an issue related to the recently-added clickthrough prevention and resize drag
* Fixed write-only properties in the inspector
* A few other minor fixes
2020-10-11 22:57:46 +11:00
2d414e544b Update README.md 2020-10-11 20:52:08 +11:00
513fcaa534 Universal click-through prevention attempt 2020-10-11 20:49:14 +11:00
b41f7211e5 2.0.3
* Fixed a few issues related to the Texture2D support/export.
2020-10-11 20:07:23 +11:00
dd6cce1df1 2.0.2 2020-10-10 20:20:10 +11:00
ad54d2c76b 2.0.2
* Added support for viewing Texture2D (and Sprite) from the Inspector, and exporting them to PNG
* Fixed an issue with generic methods not showing their return value type
* Fixed an issue where destroyed UnityEngine.Objects would cause issues in the inspector
* Fixed an issue when caching a ValueCollection of a Dictionary (the generic argument for the Entry Type is the last arg, not the first as with other Enumerables)
2020-10-10 20:19:56 +11:00
29 changed files with 804 additions and 184 deletions

View File

@ -78,11 +78,16 @@ There is a simple Mod Config for the Explorer. You can access the settings via t
* Whether or not to show the Bitwise Editing helper when inspecting integers
`Enable Tab View` (bool) | Default: `true`
* Whether or not all inspector windows a grouped into a single window with tabs.
* Whether or not all inspector windows a grouped into a single window with tabs.
`Default Output Path` (string) | Default: `Mods\Explorer`
* Where output is generated to, by default (for Texture PNG saving, etc).
## Mouse Control
Explorer can force the mouse to be visible and unlocked when the menu is open, if you have enabled "Force Unlock Mouse" (Left-Alt toggle). However, you may also want to prevent the mouse clicking-through onto the game behind Explorer, this is possible but it requires specific patches for that game.
Explorer can force the mouse to be visible and unlocked when the menu is open, if you have enabled "Force Unlock Mouse" (Left-Alt toggle). Explorer also attempts to prevent clicking-through onto the game behind the Explorer menu.
If you need more mouse control:
* For VRChat, use [VRCExplorerMouseControl](https://github.com/sinai-dev/VRCExplorerMouseControl)
* For Hellpoint, use [HPExplorerMouseControl](https://github.com/sinai-dev/Hellpoint-Mods/tree/master/HPExplorerMouseControl/HPExplorerMouseControl)
@ -93,8 +98,9 @@ For example:
using Explorer;
using Harmony; // or 'using HarmonyLib;' for BepInEx
// ...
[HarmonyPatch(typeof(MyGame.MenuClass), nameof(MyGame.MenuClass.CursorUpdate)]
public class MenuClass_CursorUpdate
// You will need to figure out the relevant Class and Method for your game using dnSpy.
[HarmonyPatch(typeof(MyGame.InputManager), nameof(MyGame.InputManager.Update)]
public class InputManager_Update
{
[HarmonyPrefix]
public static bool Prefix()

View File

@ -129,7 +129,7 @@ namespace Explorer.CacheObject
GUILayout.Label(i.ToString(), new GUILayoutOption[] { GUILayout.Width(15) });
GUILayout.Label(label, new GUILayoutOption[] { GUILayout.ExpandWidth(false) });
this.m_argumentInput[i] = GUILayout.TextField(input, new GUILayoutOption[] { GUILayout.ExpandWidth(true) });
this.m_argumentInput[i] = GUIUnstrip.TextField(input, new GUILayoutOption[] { GUILayout.ExpandWidth(true) });
GUI.skin.label.alignment = TextAnchor.MiddleLeft;

View File

@ -73,7 +73,7 @@ namespace Explorer.CacheObject
if (ret != null)
{
//m_cachedReturnValue = CacheFactory.GetTypeAndCacheObject(ret);
m_cachedReturnValue = CacheFactory.GetCacheObject(ret, IValue.ValueType);
m_cachedReturnValue = CacheFactory.GetCacheObject(ret);
m_cachedReturnValue.UpdateValue();
}
else

View File

@ -25,12 +25,26 @@ namespace Explorer.CacheObject
return;
}
//ExplorerCore.Log("Initializing InteractiveValue of type " + valueType.FullName);
InteractiveValue interactive;
if (valueType == typeof(GameObject) || valueType == typeof(Transform))
{
interactive = new InteractiveGameObject();
}
else if (valueType == typeof(Texture2D))
{
interactive = new InteractiveTexture2D();
}
else if (valueType == typeof(Texture))
{
interactive = new InteractiveTexture();
}
else if (valueType == typeof(Sprite))
{
interactive = new InteractiveSprite();
}
else if (valueType.IsPrimitive || valueType == typeof(string))
{
interactive = new InteractivePrimitive();

View File

@ -35,11 +35,26 @@ namespace Explorer.CacheObject
try
{
var pi = MemInfo as PropertyInfo;
var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
IValue.Value = pi.GetValue(target, ParseArguments());
if (pi.CanRead)
{
var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
base.UpdateValue();
IValue.Value = pi.GetValue(target, ParseArguments());
base.UpdateValue();
}
else // create a dummy value for Write-Only properties.
{
if (IValue.ValueType == typeof(string))
{
IValue.Value = "";
}
else
{
IValue.Value = Activator.CreateInstance(IValue.ValueType);
}
}
}
catch (Exception e)
{

View File

@ -19,7 +19,7 @@ namespace Explorer.Config
public int Default_Page_Limit = 20;
public bool Bitwise_Support = false;
public bool Tab_View = true;
//public bool Main_Toggle_Global = true;
public string Default_Output_Path = @"Mods\Explorer";
public static void OnLoad()
{

View File

@ -208,10 +208,14 @@
<Compile Include="CacheObject\CacheMethod.cs" />
<Compile Include="CacheObject\CacheProperty.cs" />
<Compile Include="CacheObject\CacheObjectBase.cs" />
<Compile Include="Helpers\Texture2DHelpers.cs" />
<Compile Include="UI\InteractiveValue\InteractiveValue.cs" />
<Compile Include="UI\InteractiveValue\Object\InteractiveDictionary.cs" />
<Compile Include="UI\InteractiveValue\Object\InteractiveEnumerable.cs" />
<Compile Include="UI\InteractiveValue\Object\InteractiveGameObject.cs" />
<Compile Include="UI\InteractiveValue\Object\InteractiveSprite.cs" />
<Compile Include="UI\InteractiveValue\Object\InteractiveTexture.cs" />
<Compile Include="UI\InteractiveValue\Object\InteractiveTexture2D.cs" />
<Compile Include="UI\InteractiveValue\Struct\InteractiveQuaternion.cs" />
<Compile Include="UI\InteractiveValue\Struct\InteractiveRect.cs" />
<Compile Include="UI\InteractiveValue\Struct\InteractiveVector.cs" />
@ -257,6 +261,8 @@
<Compile Include="UI\TabViewWindow.cs" />
<Compile Include="UI\WindowBase.cs" />
<Compile Include="UI\WindowManager.cs" />
<Compile Include="Unstrip\ImageConversion\ImageConversionUnstrip.cs" />
<Compile Include="Unstrip\LayerMask\LayerMaskUnstrip.cs" />
<Compile Include="Unstrip\Scene\SceneUnstrip.cs" />
<Compile Include="Unstrip\IMGUI\GUIUnstrip.cs" />
<Compile Include="Unstrip\IMGUI\Internal_LayoutUtility.cs" />

View File

@ -10,7 +10,7 @@ namespace Explorer
public class ExplorerCore
{
public const string NAME = "Explorer " + VERSION + " (" + PLATFORM + ", " + MODLOADER + ")";
public const string VERSION = "2.0.1";
public const string VERSION = "2.0.4";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.explorer";
@ -31,6 +31,12 @@ namespace Explorer
public ExplorerCore()
{
if (Instance != null)
{
Log("An instance of Explorer is already active!");
return;
}
Instance = this;
ModConfig.OnLoad();
@ -87,6 +93,11 @@ namespace Explorer
WindowManager.Instance.OnGUI();
InspectUnderMouse.OnGUI();
if (!ResizeDrag.IsMouseInResizeArea && WindowManager.IsMouseInWindow)
{
InputManager.ResetInputAxes();
}
GUI.skin = origSkin;
}

View File

@ -0,0 +1,194 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using System.IO;
using System.Reflection;
#if CPP
using Explorer.Unstrip.ImageConversion;
#endif
namespace Explorer.Helpers
{
public static class Texture2DHelpers
{
#if CPP
#else
private static bool isNewEncodeMethod = false;
private static MethodInfo EncodeToPNGMethod => m_encodeToPNGMethod ?? GetEncodeToPNGMethod();
private static MethodInfo m_encodeToPNGMethod;
private static MethodInfo GetEncodeToPNGMethod()
{
if (ReflectionHelpers.GetTypeByName("UnityEngine.ImageConversion") is Type imageConversion)
{
isNewEncodeMethod = true;
return m_encodeToPNGMethod = imageConversion.GetMethod("EncodeToPNG", ReflectionHelpers.CommonFlags);
}
var method = typeof(Texture2D).GetMethod("EncodeToPNG", ReflectionHelpers.CommonFlags);
if (method != null)
{
return m_encodeToPNGMethod = method;
}
ExplorerCore.Log("ERROR: Cannot get any EncodeToPNG method!");
return null;
}
#endif
public static bool IsReadable(this Texture2D tex)
{
try
{
// This will cause an exception if it's not readable.
// Reason for doing it this way is not all Unity versions
// ship with the 'Texture.isReadable' property.
tex.GetPixel(0, 0);
return true;
}
catch
{
return false;
}
}
public static Texture2D Copy(Texture2D other, Rect rect, bool isDTXnmNormal = false)
{
Color[] pixels;
if (!other.IsReadable())
{
other = ForceReadTexture(other, isDTXnmNormal);
}
pixels = other.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
var _newTex = new Texture2D((int)rect.width, (int)rect.height);
_newTex.SetPixels(pixels);
return _newTex;
}
public static Texture2D ForceReadTexture(Texture2D tex, bool isDTXnmNormal = false)
{
try
{
var origFilter = tex.filterMode;
tex.filterMode = FilterMode.Point;
RenderTexture rt = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.ARGB32);
rt.filterMode = FilterMode.Point;
RenderTexture.active = rt;
Graphics.Blit(tex, rt);
Texture2D _newTex = new Texture2D(tex.width, tex.height, TextureFormat.RGBA32, false);
_newTex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0);
if (isDTXnmNormal)
{
_newTex = DTXnmToRGBA(_newTex);
}
_newTex.Apply(false, false);
RenderTexture.active = null;
tex.filterMode = origFilter;
return _newTex;
}
catch (Exception e)
{
ExplorerCore.Log("Exception on ForceReadTexture: " + e.ToString());
return default;
}
}
public static void SaveTextureAsPNG(Texture2D tex, string dir, string name, bool isDTXnmNormal = false)
{
if (!Directory.Exists(dir))
{
Directory.CreateDirectory(dir);
}
byte[] data;
var savepath = dir + @"\" + name + ".png";
// Fix for non-Readable or Compressed textures.
tex = ForceReadTexture(tex, isDTXnmNormal);
if (isDTXnmNormal)
{
tex = DTXnmToRGBA(tex);
tex.Apply(false, false);
}
#if CPP
data = tex.EncodeToPNG();
#else
var method = EncodeToPNGMethod;
if (isNewEncodeMethod)
{
data = (byte[])method.Invoke(null, new object[] { tex });
}
else
{
data = (byte[])method.Invoke(tex, new object[0]);
}
#endif
if (data == null || data.Length < 1)
{
ExplorerCore.LogWarning("Couldn't get any data for the texture!");
}
else
{
#if CPP
// The IL2CPP method will return invalid byte data.
// However, we can just iterate into safe C# byte[] array.
byte[] safeData = new byte[data.Length];
for (int i = 0; i < data.Length; i++)
{
safeData[i] = (byte)data[i]; // not sure if cast is needed
}
File.WriteAllBytes(savepath, safeData);
#else
File.WriteAllBytes(savepath, data);
#endif
}
}
// Converts DTXnm-format Normal Map to RGBA-format Normal Map.
public static Texture2D DTXnmToRGBA(Texture2D tex)
{
Color[] colors = tex.GetPixels();
for (int i = 0; i < colors.Length; i++)
{
Color c = colors[i];
c.r = c.a * 2 - 1; // red <- alpha
c.g = c.g * 2 - 1; // green is always the same
Vector2 rg = new Vector2(c.r, c.g); //this is the red-green vector
c.b = Mathf.Sqrt(1 - Mathf.Clamp01(Vector2.Dot(rg, rg))); //recalculate the blue channel
colors[i] = new Color(
(c.r * 0.5f) + 0.5f,
(c.g * 0.5f) + 0.25f,
(c.b * 0.5f) + 0.5f
);
}
var newtex = new Texture2D(tex.width, tex.height, TextureFormat.RGBA32, false);
newtex.SetPixels(colors);
return newtex;
}
}
}

View File

@ -42,6 +42,16 @@ namespace Explorer
public static bool GetMouseButtonDown(int btn) => inputModule.GetMouseButtonDown(btn);
public static bool GetMouseButton(int btn) => inputModule.GetMouseButton(btn);
#if CPP
internal delegate void d_ResetInputAxes();
internal static d_ResetInputAxes ResetInputAxes_iCall =
IL2CPP.ResolveICall<d_ResetInputAxes>("UnityEngine.Input::ResetInputAxes");
public static void ResetInputAxes() => ResetInputAxes_iCall();
#else
public static void ResetInputAxes() => UnityEngine.Input.ResetInputAxes();
#endif
//#if CPP
//#pragma warning disable IDE1006
// // public extern static string compositionString { get; }

View File

@ -4,6 +4,7 @@ using System;
using UnityEngine;
using System.Reflection;
using System.Runtime.InteropServices;
using Explorer.UI.Shared;
#if CPP
using UnhollowerBaseLib;
using UnityEngine.SceneManagement;
@ -14,16 +15,13 @@ namespace Explorer.Tests
public static class StaticTestClass
{
public static int StaticProperty => 5;
public static int StaticField = 69;
public static List<string> StaticList = new List<string>
{
"one",
"two",
"three",
};
public static void StaticMethod() { }
}
@ -33,6 +31,17 @@ namespace Explorer.Tests
public static TestClass Instance => m_instance ?? (m_instance = new TestClass());
private static TestClass m_instance;
public static bool ReadSetOnlyProperty => m_setOnlyProperty;
public static bool SetOnlyProperty
{
set => m_setOnlyProperty = value;
}
private static bool m_setOnlyProperty;
public Texture2D TestTexture = UIStyles.MakeTex(200, 200, Color.white);
public static Sprite TestSprite;
public static int StaticProperty => 5;
public static int StaticField = 5;
public int NonStaticField;
@ -52,6 +61,16 @@ namespace Explorer.Tests
public TestClass()
{
#if CPP
TestTexture.name = "TestTexture";
var r = new Rect(0, 0, TestTexture.width, TestTexture.height);
var v2 = Vector2.zero;
var v4 = Vector4.zero;
TestSprite = Sprite.CreateSprite_Injected(TestTexture, ref r, ref v2, 100f, 0u, SpriteMeshType.Tight, ref v4, false);
GameObject.DontDestroyOnLoad(TestTexture);
GameObject.DontDestroyOnLoad(TestSprite);
ILHashSetTest = new Il2CppSystem.Collections.Generic.HashSet<string>();
ILHashSetTest.Add("1");
ILHashSetTest.Add("2");

View File

@ -17,32 +17,25 @@ namespace Explorer.UI
}
private static bool m_forceUnlock;
private static CursorLockMode m_lastLockMode;
private static bool m_lastVisibleState;
private static bool m_currentlySettingCursor = false;
public static bool ShouldForceMouse => ExplorerCore.ShowMenu && Unlock;
private static Type CursorType => m_cursorType ?? (m_cursorType = ReflectionHelpers.GetTypeByName("UnityEngine.Cursor"));
private static CursorLockMode m_lastLockMode;
private static bool m_lastVisibleState;
private static bool m_currentlySettingCursor = false;
private static Type CursorType
=> m_cursorType
?? (m_cursorType = ReflectionHelpers.GetTypeByName("UnityEngine.Cursor"));
private static Type m_cursorType;
public static void Init()
{
try
{
// Check if Cursor class is loaded
if (CursorType == null)
{
ExplorerCore.Log("Trying to manually load Cursor module...");
if (ReflectionHelpers.LoadModule("UnityEngine.CoreModule") && CursorType != null)
{
ExplorerCore.Log("Ok!");
}
else
{
throw new Exception("Could not load UnityEngine.Cursor module!");
}
throw new Exception("Could not find Type 'UnityEngine.Cursor'!");
}
// Get current cursor state and enable cursor
@ -91,7 +84,8 @@ namespace Explorer.UI
}
catch (Exception e)
{
ExplorerCore.Log($"[NON-FATAL] Couldn't patch a method: {e.Message}");
string s = setter ? "set_" : "get_" ;
ExplorerCore.Log($"Unable to patch Cursor.{s}{property}: {e.Message}");
}
}

View File

@ -3,6 +3,7 @@ using System.Collections.Generic;
using UnityEngine;
using Explorer.UI.Shared;
using Explorer.UI.Main;
using Explorer.Unstrip.LayerMasks;
#if CPP
using UnhollowerRuntimeLib;
#endif
@ -17,6 +18,8 @@ namespace Explorer.UI.Inspectors
public GameObject TargetGO;
public bool pendingDestroy;
private static bool m_hideControls;
// gui element holders
@ -43,6 +46,8 @@ namespace Explorer.UI.Inspectors
private bool m_autoUpdateTransform;
private bool m_localContext;
private int m_layer;
private readonly List<Component> m_cachedDestroyList = new List<Component>();
private string m_addComponentInput = "";
@ -104,16 +109,16 @@ namespace Explorer.UI.Inspectors
{
try
{
if (pendingDestroy) return;
if (Target == null)
{
ExplorerCore.Log("Target is null!");
DestroyWindow();
DestroyOnException(new Exception("Target was destroyed."));
return;
}
if (!TargetGO && !GetObjectAsGameObject())
{
ExplorerCore.Log("Target was destroyed!");
DestroyWindow();
DestroyOnException(new Exception("Target was destroyed."));
return;
}
@ -132,6 +137,8 @@ namespace Explorer.UI.Inspectors
TargetGO.transform.localScale = m_frozenScale;
}
m_layer = TargetGO.layer;
// update child objects
var childList = new List<Transform>();
for (int i = 0; i < TargetGO.transform.childCount; i++)
@ -163,6 +170,7 @@ namespace Explorer.UI.Inspectors
private void DestroyOnException(Exception e)
{
ExplorerCore.Log($"Exception drawing GameObject Window: {e.GetType()}, {e.Message}");
pendingDestroy = true;
DestroyWindow();
}
@ -204,6 +212,8 @@ namespace Explorer.UI.Inspectors
public override void WindowFunction(int windowID)
{
if (pendingDestroy) return;
try
{
var rect = WindowManager.TabView ? TabViewWindow.Instance.m_rect : this.m_rect;
@ -250,6 +260,8 @@ namespace Explorer.UI.Inspectors
GUIUnstrip.TextArea(m_name, new GUILayoutOption[0]);
GUILayout.EndHorizontal();
LayerControls();
// --- Horizontal Columns section ---
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
@ -280,6 +292,34 @@ namespace Explorer.UI.Inspectors
}
}
private void LayerControls()
{
GUIUnstrip.BeginHorizontal();
GUILayout.Label("Layer:", new GUILayoutOption[] { GUILayout.Width(50) });
if (GUILayout.Button("<", new GUILayoutOption[] { GUILayout.Width(30) }))
{
if (m_layer > 0)
{
m_layer--;
if (TargetGO) TargetGO.layer = m_layer;
}
}
if (GUILayout.Button(">", new GUILayoutOption[] { GUILayout.Width(30) }))
{
if (m_layer < 32)
{
m_layer++;
if (TargetGO) TargetGO.layer = m_layer;
}
}
GUILayout.Label($"{m_layer} (<color=cyan>{LayerMaskUnstrip.LayerToName(m_layer)}</color>)",
new GUILayoutOption[] { GUILayout.Width(200) });
GUILayout.EndHorizontal();
}
private void TransformList(Rect m_rect)
{
GUIUnstrip.BeginVertical(GUIContent.none, GUI.skin.box, null);

View File

@ -160,7 +160,7 @@ namespace Explorer.UI.Inspectors
{
try
{
// make sure member type is Field, Method of Property (4 / 8 / 16)
// make sure member type is Field, Method or Property (4 / 8 / 16)
int m = (int)member.MemberType;
if (m < 4 || m > 16)
continue;
@ -213,6 +213,7 @@ namespace Explorer.UI.Inspectors
try
{
var cached = CacheFactory.GetCacheObject(member, target);
if (cached != null)
{
cachedSigs.Add(sig);

View File

@ -146,7 +146,7 @@ namespace Explorer.UI
{
GUILayout.Label($"<color=grey><i>Not yet evaluated</i></color> ({typeName})", new GUILayoutOption[0]);
}
else if (Value == null && !(cacheMember is CacheMethod))
else if ((Value == null || Value is UnityEngine.Object uObj && !uObj) && !(cacheMember is CacheMethod))
{
GUILayout.Label($"<i>null ({typeName})</i>", new GUILayoutOption[0]);
}
@ -198,31 +198,33 @@ namespace Explorer.UI
return m_toStringMethod;
}
private string GetButtonLabel()
public string GetButtonLabel()
{
if (Value == null) return null;
var valueType = ReflectionHelpers.GetActualType(Value);
string label = (string)ToStringMethod?.Invoke(Value, null) ?? Value.ToString();
var classColor = ValueType.IsAbstract && ValueType.IsSealed
var classColor = valueType.IsAbstract && valueType.IsSealed
? Syntax.Class_Static
: Syntax.Class_Instance;
string typeLabel = $"<color={classColor}>{ValueType.FullName}</color>";
string typeLabel = $"<color={classColor}>{valueType.FullName}</color>";
if (Value is UnityEngine.Object)
{
label = label.Replace($"({ValueType.FullName})", $"({typeLabel})");
label = label.Replace($"({valueType.FullName})", $"({typeLabel})");
}
else
{
if (!label.Contains(ValueType.FullName))
if (!label.Contains(valueType.FullName))
{
label += $" ({typeLabel})";
}
else
{
label = label.Replace(ValueType.FullName, typeLabel);
label = label.Replace(valueType.FullName, typeLabel);
}
}

View File

@ -10,6 +10,9 @@ using Explorer.CacheObject;
namespace Explorer.UI
{
// TODO: Re-work class using InteractiveEnumerable or maybe InteractiveCollection for the Keys/Value lists.
// Make the keys and values editable.
public class InteractiveDictionary : InteractiveValue, IExpandHeight
{
public bool IsExpanded { get; set; }

View File

@ -173,38 +173,20 @@ namespace Explorer.UI
private Type GetEntryType()
{
if (m_entryType == null)
if (ValueType.IsGenericType)
{
if (OwnerCacheObject is CacheMember cacheMember && cacheMember.MemInfo != null)
{
Type memberType = null;
switch (cacheMember.MemInfo.MemberType)
{
case MemberTypes.Field:
memberType = (cacheMember.MemInfo as FieldInfo).FieldType;
break;
case MemberTypes.Property:
memberType = (cacheMember.MemInfo as PropertyInfo).PropertyType;
break;
}
var gArgs = ValueType.GetGenericArguments();
if (memberType != null && memberType.IsGenericType)
{
m_entryType = memberType.GetGenericArguments()[0];
}
}
else if (Value != null)
if (ValueType.FullName.Contains("ValueCollection"))
{
var type = Value.GetType();
if (type.IsGenericType)
{
m_entryType = type.GetGenericArguments()[0];
}
m_entryType = gArgs[gArgs.Length - 1];
}
else
{
m_entryType = gArgs[0];
}
}
// use System.Object for non-generic.
if (m_entryType == null)
else
{
m_entryType = typeof(object);
}
@ -255,18 +237,11 @@ namespace Explorer.UI
}
#endif
//ExplorerCore.Log("Caching enumeration entry " + obj.ToString() + " as " + EntryType.FullName);
var cached = new CacheEnumerated() { Index = index, RefIList = Value as IList, ParentEnumeration = this };
cached.Init(obj, EntryType);
list.Add(cached);
//if (CacheFactory.GetCacheObject(obj, t) is CacheObjectBase cached)
//{
// list.Add(cached);
//}
//else
//{
// list.Add(null);
//}
}
else
{

View File

@ -0,0 +1,51 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Explorer.Helpers;
using UnityEngine;
namespace Explorer.UI
{
public class InteractiveSprite : InteractiveTexture2D
{
private Sprite refSprite;
public override void UpdateValue()
{
#if CPP
if (Value != null && Value.Il2CppCast(typeof(Sprite)) is Sprite sprite)
{
refSprite = sprite;
}
#else
if (Value is Sprite sprite)
{
refSprite = sprite;
}
#endif
base.UpdateValue();
}
public override void GetTexture2D()
{
if (refSprite)
{
currentTex = refSprite.texture;
}
}
public override void GetGUIContent()
{
// Check if the Sprite.textureRect is just the entire texture
if (refSprite.textureRect != new Rect(0, 0, currentTex.width, currentTex.height))
{
// It's not, do a sub-copy.
currentTex = Texture2DHelpers.Copy(refSprite.texture, refSprite.textureRect);
}
base.GetGUIContent();
}
}
}

View File

@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace Explorer.UI
{
// This class is possibly unnecessary.
// It's just for CacheMembers that have 'Texture' as the value type, but is actually a Texture2D.
public class InteractiveTexture : InteractiveTexture2D
{
public override void GetTexture2D()
{
#if CPP
if (Value != null && Value.Il2CppCast(typeof(Texture2D)) is Texture2D tex)
#else
if (Value is Texture2D tex)
#endif
{
currentTex = tex;
texContent = new GUIContent
{
image = currentTex
};
}
}
}
}

View File

@ -0,0 +1,149 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Explorer.CacheObject;
using Explorer.Config;
using UnityEngine;
using System.IO;
using Explorer.Helpers;
#if CPP
using Explorer.Unstrip.ImageConversion;
#endif
namespace Explorer.UI
{
public class InteractiveTexture2D : InteractiveValue, IExpandHeight
{
public bool IsExpanded { get; set; }
public float WhiteSpace { get; set; } = 215f;
public Texture2D currentTex;
public GUIContent texContent;
private string saveFolder = ModConfig.Instance.Default_Output_Path;
public override void Init()
{
base.Init();
}
public override void UpdateValue()
{
base.UpdateValue();
GetTexture2D();
}
public virtual void GetTexture2D()
{
#if CPP
if (Value != null && Value.Il2CppCast(typeof(Texture2D)) is Texture2D tex)
#else
if (Value is Texture2D tex)
#endif
{
currentTex = tex;
}
}
public virtual void GetGUIContent()
{
texContent = new GUIContent
{
image = currentTex
};
}
public override void DrawValue(Rect window, float width)
{
GUIUnstrip.BeginVertical();
GUIUnstrip.BeginHorizontal();
if (currentTex && !IsExpanded)
{
if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = true;
GetGUIContent();
}
}
else if (currentTex)
{
if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) }))
{
IsExpanded = false;
}
}
base.DrawValue(window, width);
GUILayout.EndHorizontal();
if (currentTex && IsExpanded)
{
DrawTextureControls();
DrawTexture();
}
GUILayout.EndVertical();
}
// Temporarily disabled in BepInEx IL2CPP.
private void DrawTexture()
{
#if CPP
#if BIE
#else
GUILayout.Label(texContent, new GUILayoutOption[0]);
#endif
#else
GUILayout.Label(texContent, new GUILayoutOption[0]);
#endif
}
private void DrawTextureControls()
{
GUIUnstrip.BeginHorizontal();
GUILayout.Label("Save folder:", new GUILayoutOption[] { GUILayout.Width(80f) });
saveFolder = GUIUnstrip.TextField(saveFolder, new GUILayoutOption[0]);
GUIUnstrip.Space(10f);
GUILayout.EndHorizontal();
if (GUILayout.Button("Save to PNG", new GUILayoutOption[] { GUILayout.Width(100f) }))
{
var name = RemoveInvalidFilenameChars(currentTex.name ?? "");
if (string.IsNullOrEmpty(name))
{
if (OwnerCacheObject is CacheMember cacheMember)
{
name = cacheMember.MemInfo.Name;
}
else
{
name = "UNTITLED";
}
}
Texture2DHelpers.SaveTextureAsPNG(currentTex, saveFolder, name, false);
ExplorerCore.Log($@"Saved to {saveFolder}\{name}.png!");
}
}
private string RemoveInvalidFilenameChars(string s)
{
var invalid = System.IO.Path.GetInvalidFileNameChars();
foreach (var c in invalid)
{
s = s.Replace(c.ToString(), "");
}
return s;
}
}
}

View File

@ -80,12 +80,13 @@ namespace Explorer.UI
if (OwnerCacheObject.CanWrite)
{
b = GUILayout.Toggle(b, label, new GUILayoutOption[0]);
if (b != (bool)Value)
{
Value = b;
OwnerCacheObject.SetValue();
}
Value = GUILayout.Toggle(b, label, new GUILayoutOption[] { GUILayout.Width(60) });
DrawApplyButton();
//if (b != (bool)Value)
//{
// Value = b;
// OwnerCacheObject.SetValue();
//}
}
else
{
@ -104,13 +105,8 @@ namespace Explorer.UI
GUILayout.Label("<color=#2df7b2><i>" + ValueType.Name + "</i></color>", new GUILayoutOption[] { GUILayout.Width(50) });
m_valueToString = GUIUnstrip.TextArea(m_valueToString, new GUILayoutOption[] { GUILayout.ExpandWidth(true) });
if (OwnerCacheObject.CanWrite)
{
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
{
SetValueFromInput();
}
}
DrawApplyButton();
if (ModConfig.Instance.Bitwise_Support && m_canBitwiseOperate)
{
@ -129,6 +125,24 @@ namespace Explorer.UI
GUILayout.EndVertical();
}
private void DrawApplyButton()
{
if (OwnerCacheObject.CanWrite)
{
if (GUILayout.Button("<color=#00FF00>Apply</color>", new GUILayoutOption[] { GUILayout.Width(60) }))
{
if (m_isBool)
{
OwnerCacheObject.SetValue();
}
else
{
SetValueFromInput();
}
}
}
}
private void DrawBitwise()
{
if (OwnerCacheObject.CanWrite)

View File

@ -18,12 +18,14 @@ namespace Explorer.UI.Main
public int defaultPageLimit;
public bool bitwiseSupport;
public bool tabView;
public string defaultOutputPath;
private CacheObjectBase toggleKeyInput;
private CacheObjectBase defaultSizeInput;
private CacheObjectBase defaultPageLimitInput;
private CacheObjectBase bitwiseSupportInput;
private CacheObjectBase tabViewInput;
private CacheObjectBase defaultOutputPathInput;
public override void Init()
{
@ -41,6 +43,9 @@ namespace Explorer.UI.Main
tabView = ModConfig.Instance.Tab_View;
tabViewInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("tabView"), this);
defaultOutputPath = ModConfig.Instance.Default_Output_Path;
defaultOutputPathInput = CacheFactory.GetCacheObject(typeof(OptionsPage).GetField("defaultOutputPath"), this);
}
public override void Update() { }
@ -58,26 +63,41 @@ namespace Explorer.UI.Main
toggleKeyInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
GUILayout.EndHorizontal();
UIStyles.HorizontalLine(Color.black, true);
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label($"Default Window Size:", new GUILayoutOption[] { GUILayout.Width(215f) });
defaultSizeInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
GUILayout.EndHorizontal();
UIStyles.HorizontalLine(Color.black, true);
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label($"Default Items per Page:", new GUILayoutOption[] { GUILayout.Width(215f) });
defaultPageLimitInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
GUILayout.EndHorizontal();
UIStyles.HorizontalLine(Color.black, true);
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label($"Enable Bitwise Editing:", new GUILayoutOption[] { GUILayout.Width(215f) });
bitwiseSupportInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
GUILayout.EndHorizontal();
UIStyles.HorizontalLine(Color.black, true);
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label($"Enable Tab View:", new GUILayoutOption[] { GUILayout.Width(215f) });
tabViewInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
GUILayout.EndHorizontal();
UIStyles.HorizontalLine(Color.black, true);
GUIUnstrip.BeginHorizontal(new GUILayoutOption[0]);
GUILayout.Label($"Default Output Path:", new GUILayoutOption[] { GUILayout.Width(215f) });
defaultOutputPathInput.IValue.DrawValue(MainMenu.MainRect, MainMenu.MainRect.width - 215f);
GUILayout.EndHorizontal();
if (GUILayout.Button("<color=lime><b>Apply and Save</b></color>", new GUILayoutOption[0]))
{
ApplyAndSave();

View File

@ -161,8 +161,15 @@ namespace Explorer.UI.Main
GUIUnstrip.EndScrollView();
GUILayout.EndVertical();
}
catch
catch (Exception e)
{
ExplorerCore.Log("Exception drawing search results!");
while (e != null)
{
ExplorerCore.Log(e);
e = e.InnerException;
}
m_searchResults.Clear();
}
}

View File

@ -8,18 +8,17 @@ namespace Explorer.UI.Shared
{
public class ResizeDrag
{
#if CPP
private static bool RESIZE_FAILED = false;
#endif
public static bool IsResizing = false;
public static bool IsMouseInResizeArea = false;
private static readonly GUIContent gcDrag = new GUIContent("<-- Drag to resize -->");
private static bool isResizing = false;
private static Rect m_currentResize;
private static int m_currentWindow;
public static Rect ResizeWindow(Rect _rect, int ID)
{
#if CPP
if (!RESIZE_FAILED)
{
var origRect = _rect;
@ -29,31 +28,40 @@ namespace Explorer.UI.Shared
GUIUnstrip.BeginHorizontal(GUIContent.none, GUI.skin.box, null);
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
#if ML
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
#if BIE
#if CPP // Temporary for BepInEx IL2CPP
GUILayout.Button("<-- Drag to resize -->", new GUILayoutOption[] { GUILayout.Height(15) });
#else
GUILayout.Button("<-- Drag to resize -->", new GUILayoutOption[] { GUILayout.Height(15) });
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
#endif
#else
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
#endif
var r = GUIUnstrip.GetLastRect();
var resizeDragArea = GUIUnstrip.GetLastRect();
var mousePos = InputManager.MousePosition;
try
{
var mouse = GUIUnstrip.ScreenToGUIPoint(new Vector2(mousePos.x, Screen.height - mousePos.y));
if (r.Contains(mouse) && InputManager.GetMouseButtonDown(0))
if (resizeDragArea.Contains(mouse))
{
isResizing = true;
m_currentWindow = ID;
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
IsMouseInResizeArea = true;
if (InputManager.GetMouseButton(0))
{
IsResizing = true;
m_currentWindow = ID;
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
}
}
else if (!InputManager.GetMouseButton(0))
{
isResizing = false;
IsMouseInResizeArea = false;
IsResizing = false;
}
if (isResizing && ID == m_currentWindow)
if (IsResizing && ID == m_currentWindow)
{
_rect.width = Mathf.Max(100, m_currentResize.width + (mouse.x - m_currentResize.x));
_rect.height = Mathf.Max(100, m_currentResize.height + (mouse.y - m_currentResize.y));
@ -81,8 +89,6 @@ namespace Explorer.UI.Shared
//ExplorerCore.Log(e.StackTrace);
return origRect;
}
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
else
{
@ -111,44 +117,8 @@ namespace Explorer.UI.Shared
}
GUILayout.EndHorizontal();
GUI.skin.label.alignment = TextAnchor.UpperLeft;
}
#else // mono
GUIUnstrip.BeginHorizontal(GUIContent.none, GUI.skin.box, null);
GUI.skin.label.alignment = TextAnchor.MiddleCenter;
GUILayout.Button(gcDrag, GUI.skin.label, new GUILayoutOption[] { GUILayout.Height(15) });
//var r = GUILayoutUtility.GetLastRect();
var r = GUILayoutUtility.GetLastRect();
var mousePos = InputManager.MousePosition;
var mouse = GUIUnstrip.ScreenToGUIPoint(new Vector2(mousePos.x, Screen.height - mousePos.y));
if (r.Contains(mouse) && InputManager.GetMouseButtonDown(0))
{
isResizing = true;
m_currentWindow = ID;
m_currentResize = new Rect(mouse.x, mouse.y, _rect.width, _rect.height);
}
else if (!InputManager.GetMouseButton(0))
{
isResizing = false;
}
if (isResizing && ID == m_currentWindow)
{
_rect.width = Mathf.Max(100, m_currentResize.width + (mouse.x - m_currentResize.x));
_rect.height = Mathf.Max(100, m_currentResize.height + (mouse.y - m_currentResize.y));
_rect.xMax = Mathf.Min(Screen.width, _rect.xMax); // modifying xMax affects width, not x
_rect.yMax = Mathf.Min(Screen.height, _rect.yMax); // modifying yMax affects height, not y
}
GUILayout.EndHorizontal();
#endif
GUI.skin.label.alignment = TextAnchor.MiddleLeft;
return _rect;

View File

@ -70,6 +70,15 @@ namespace Explorer.UI
int rowCount = 0;
for (int i = 0; i < WindowManager.Windows.Count; i++)
{
var window = WindowManager.Windows[i];
// Prevent trying to draw destroyed UnityEngine.Objects
// before the WindowManager removes them.
if (window.Target is UnityEngine.Object uObj && !uObj)
{
continue;
}
if (rowCount >= tabPerRow)
{
rowCount = 0;
@ -82,7 +91,6 @@ namespace Explorer.UI
string color = focused ? "<color=lime>" : "<color=orange>";
GUI.color = focused ? Color.green : Color.white;
var window = WindowManager.Windows[i];
if (GUILayout.Button(color + window.Title + "</color>", new GUILayoutOption[] { GUILayout.Width(200) }))
{
TargetTabID = i;

View File

@ -11,6 +11,26 @@ namespace Explorer.UI
public static bool TabView = Config.ModConfig.Instance.Tab_View;
public static bool IsMouseInWindow
{
get
{
if (!ExplorerCore.ShowMenu)
{
return false;
}
foreach (var window in Windows)
{
if (RectContainsMouse(window.m_rect))
{
return true;
}
}
return RectContainsMouse(MainMenu.MainRect);
}
}
public static List<WindowBase> Windows = new List<WindowBase>();
public static int CurrentWindowID { get; set; } = 500000;
private static Rect m_lastWindowRect;
@ -123,26 +143,6 @@ namespace Explorer.UI
return new_window;
}
public static bool IsMouseInWindow
{
get
{
if (!ExplorerCore.ShowMenu)
{
return false;
}
foreach (var window in Windows)
{
if (RectContainsMouse(window.m_rect))
{
return true;
}
}
return RectContainsMouse(MainMenu.MainRect);
}
}
private static bool RectContainsMouse(Rect rect)
{
var mousePos = InputManager.MousePosition;

View File

@ -0,0 +1,51 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnhollowerBaseLib;
using UnityEngine;
using System.IO;
namespace Explorer.Unstrip.ImageConversion
{
public static class ImageConversionUnstrip
{
// byte[] ImageConversion.EncodeToPNG(this Texture2D image);
public static byte[] EncodeToPNG(this Texture2D tex)
{
return EncodeToPNG_iCall(tex.Pointer);
}
internal delegate byte[] EncodeToPNG_delegate(IntPtr tex);
internal static EncodeToPNG_delegate EncodeToPNG_iCall =
IL2CPP.ResolveICall<EncodeToPNG_delegate>("UnityEngine.ImageConversion::EncodeToPNG");
// bool ImageConversion.LoadImage(this Texture2D tex, byte[] data, bool markNonReadable);
public static bool LoadImage(this Texture2D tex, byte[] data, bool markNonReadable)
{
return LoadImage_iCall(tex.Pointer, data, markNonReadable);
}
internal delegate bool LoadImage_delegate(IntPtr tex, byte[] data, bool markNonReadable);
internal static LoadImage_delegate LoadImage_iCall =
IL2CPP.ResolveICall<LoadImage_delegate>("UnityEngine.ImageConversion::LoadImage");
// Helper for LoadImage
public static bool LoadImage(this Texture2D tex, string filePath, bool markNonReadable)
{
if (!File.Exists(filePath))
{
return false;
}
var data = File.ReadAllBytes(filePath);
return tex.LoadImage(data, markNonReadable);
}
}
}
#endif

View File

@ -0,0 +1,32 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
#if CPP
using UnhollowerBaseLib;
#endif
namespace Explorer.Unstrip.LayerMasks
{
public static class LayerMaskUnstrip
{
#if CPP
internal delegate IntPtr d_LayerToName(int layer);
internal static d_LayerToName LayerToName_iCall =
IL2CPP.ResolveICall<d_LayerToName>("UnityEngine.LayerMask::LayerToName");
public static string LayerToName(int layer)
{
var ptr = LayerToName_iCall(layer);
return IL2CPP.Il2CppStringToManaged(ptr);
}
#else
public static string LayerToName(int layer)
{
return LayerMask.LayerToName(layer);
}
#endif
}
}

View File

@ -5,37 +5,35 @@ using System.Linq;
using System.Text;
using UnhollowerBaseLib;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace Explorer.Unstrip.Scene
namespace Explorer.Unstrip.Scenes
{
public class SceneUnstrip
{
internal delegate void getRootSceneObjects(int handle, IntPtr list);
internal static getRootSceneObjects getRootSceneObjects_iCall =
IL2CPP.ResolveICall<getRootSceneObjects>("UnityEngine.SceneManagement.Scene::GetRootGameObjectsInternal");
public static void GetRootGameObjects_Internal(UnityEngine.SceneManagement.Scene scene, IntPtr list)
{
getRootSceneObjects_iCall(scene.handle, list);
}
public static GameObject[] GetRootSceneObjects(UnityEngine.SceneManagement.Scene scene)
//Scene.GetRootGameObjects();
public static GameObject[] GetRootGameObjects(Scene scene)
{
var list = new Il2CppSystem.Collections.Generic.List<GameObject>(GetRootCount_Internal(scene));
GetRootGameObjects_Internal(scene, list.Pointer);
GetRootGameObjectsInternal_iCall(scene.handle, list.Pointer);
return list.ToArray();
}
internal delegate int getRootCount(int handle);
internal static getRootCount getRootCount_iCall =
IL2CPP.ResolveICall<getRootCount>("UnityEngine.SceneManagement.Scene::GetRootCountInternal");
internal delegate void GetRootGameObjectsInternal_delegate(int handle, IntPtr list);
internal static GetRootGameObjectsInternal_delegate GetRootGameObjectsInternal_iCall =
IL2CPP.ResolveICall<GetRootGameObjectsInternal_delegate>("UnityEngine.SceneManagement.Scene::GetRootGameObjectsInternal");
public static int GetRootCount_Internal(UnityEngine.SceneManagement.Scene scene)
//Scene.rootCount;
public static int GetRootCount_Internal(Scene scene)
{
return getRootCount_iCall(scene.handle);
return GetRootCountInternal_iCall(scene.handle);
}
internal delegate int GetRootCountInternal_delegate(int handle);
internal static GetRootCountInternal_delegate GetRootCountInternal_iCall =
IL2CPP.ResolveICall<GetRootCountInternal_delegate>("UnityEngine.SceneManagement.Scene::GetRootCountInternal");
}
}
#endif