From b41f7211e5fd8e762f8a4748e070f1584ee7b577 Mon Sep 17 00:00:00 2001 From: sinaioutlander <49360850+sinaioutlander@users.noreply.github.com> Date: Sun, 11 Oct 2020 20:07:23 +1100 Subject: [PATCH] 2.0.3 * Fixed a few issues related to the Texture2D support/export. --- src/CacheObject/CacheObjectBase.cs | 4 + src/Explorer.csproj | 2 + src/ExplorerCore.cs | 2 +- src/Helpers/Texture2DHelpers.cs | 194 ++++++++++++++++++ src/UI/Inspectors/ReflectionInspector.cs | 1 + src/UI/InteractiveValue/InteractiveValue.cs | 14 +- .../Object/InteractiveDictionary.cs | 3 + .../Object/InteractiveSprite.cs | 37 +++- .../Object/InteractiveTexture.cs | 30 +++ .../Object/InteractiveTexture2D.cs | 169 +++------------ src/UI/Main/SearchPage.cs | 9 +- 11 files changed, 313 insertions(+), 152 deletions(-) create mode 100644 src/Helpers/Texture2DHelpers.cs create mode 100644 src/UI/InteractiveValue/Object/InteractiveTexture.cs diff --git a/src/CacheObject/CacheObjectBase.cs b/src/CacheObject/CacheObjectBase.cs index 16e69b2..82d84eb 100644 --- a/src/CacheObject/CacheObjectBase.cs +++ b/src/CacheObject/CacheObjectBase.cs @@ -37,6 +37,10 @@ namespace Explorer.CacheObject { interactive = new InteractiveTexture2D(); } + else if (valueType == typeof(Texture)) + { + interactive = new InteractiveTexture(); + } else if (valueType == typeof(Sprite)) { interactive = new InteractiveSprite(); diff --git a/src/Explorer.csproj b/src/Explorer.csproj index 3200986..2a3aa9b 100644 --- a/src/Explorer.csproj +++ b/src/Explorer.csproj @@ -208,11 +208,13 @@ + + diff --git a/src/ExplorerCore.cs b/src/ExplorerCore.cs index abc9ddb..29a85ac 100644 --- a/src/ExplorerCore.cs +++ b/src/ExplorerCore.cs @@ -10,7 +10,7 @@ namespace Explorer public class ExplorerCore { public const string NAME = "Explorer " + VERSION + " (" + PLATFORM + ", " + MODLOADER + ")"; - public const string VERSION = "2.0.2"; + public const string VERSION = "2.0.3"; public const string AUTHOR = "Sinai"; public const string GUID = "com.sinai.explorer"; diff --git a/src/Helpers/Texture2DHelpers.cs b/src/Helpers/Texture2DHelpers.cs new file mode 100644 index 0000000..2875b8e --- /dev/null +++ b/src/Helpers/Texture2DHelpers.cs @@ -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; + } + } +} diff --git a/src/UI/Inspectors/ReflectionInspector.cs b/src/UI/Inspectors/ReflectionInspector.cs index cbc802c..a804ff2 100644 --- a/src/UI/Inspectors/ReflectionInspector.cs +++ b/src/UI/Inspectors/ReflectionInspector.cs @@ -213,6 +213,7 @@ namespace Explorer.UI.Inspectors try { var cached = CacheFactory.GetCacheObject(member, target); + if (cached != null) { cachedSigs.Add(sig); diff --git a/src/UI/InteractiveValue/InteractiveValue.cs b/src/UI/InteractiveValue/InteractiveValue.cs index 5b2ffad..e873119 100644 --- a/src/UI/InteractiveValue/InteractiveValue.cs +++ b/src/UI/InteractiveValue/InteractiveValue.cs @@ -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 = $"{ValueType.FullName}"; + string typeLabel = $"{valueType.FullName}"; 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); } } diff --git a/src/UI/InteractiveValue/Object/InteractiveDictionary.cs b/src/UI/InteractiveValue/Object/InteractiveDictionary.cs index 850a211..8dc1224 100644 --- a/src/UI/InteractiveValue/Object/InteractiveDictionary.cs +++ b/src/UI/InteractiveValue/Object/InteractiveDictionary.cs @@ -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; } diff --git a/src/UI/InteractiveValue/Object/InteractiveSprite.cs b/src/UI/InteractiveValue/Object/InteractiveSprite.cs index cfed55b..e6cc7c8 100644 --- a/src/UI/InteractiveValue/Object/InteractiveSprite.cs +++ b/src/UI/InteractiveValue/Object/InteractiveSprite.cs @@ -2,27 +2,50 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using Explorer.Helpers; using UnityEngine; namespace Explorer.UI { public class InteractiveSprite : InteractiveTexture2D { - public override void GetTexture2D() + 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) -#endif { - currentTex = sprite.texture; - texContent = new GUIContent - { - image = currentTex - }; + 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(); + } } } diff --git a/src/UI/InteractiveValue/Object/InteractiveTexture.cs b/src/UI/InteractiveValue/Object/InteractiveTexture.cs new file mode 100644 index 0000000..9c73d56 --- /dev/null +++ b/src/UI/InteractiveValue/Object/InteractiveTexture.cs @@ -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 + }; + } + } + } +} diff --git a/src/UI/InteractiveValue/Object/InteractiveTexture2D.cs b/src/UI/InteractiveValue/Object/InteractiveTexture2D.cs index ed2cf38..60e83e0 100644 --- a/src/UI/InteractiveValue/Object/InteractiveTexture2D.cs +++ b/src/UI/InteractiveValue/Object/InteractiveTexture2D.cs @@ -6,6 +6,7 @@ using Explorer.CacheObject; using Explorer.Config; using UnityEngine; using System.IO; +using Explorer.Helpers; #if CPP using Explorer.Unstrip.ImageConversion; #endif @@ -42,35 +43,37 @@ namespace Explorer.UI if (Value is Texture2D tex) #endif { - currentTex = tex; - texContent = new GUIContent - { - image = currentTex - }; + 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) + if (currentTex && !IsExpanded) { - if (!IsExpanded) + if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) })) { - if (GUILayout.Button("v", new GUILayoutOption[] { GUILayout.Width(25) })) - { - IsExpanded = true; - } + IsExpanded = true; + GetGUIContent(); } - else + } + else if (currentTex) + { + if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) { - if (GUILayout.Button("^", new GUILayoutOption[] { GUILayout.Width(25) })) - { - IsExpanded = false; - } + IsExpanded = false; } } @@ -112,29 +115,22 @@ namespace Explorer.UI if (GUILayout.Button("Save to PNG", new GUILayoutOption[] { GUILayout.Width(100f) })) { - if (currentTex) + var name = RemoveInvalidFilenameChars(currentTex.name ?? ""); + if (string.IsNullOrEmpty(name)) { - var name = RemoveInvalidFilenameChars(currentTex.name ?? ""); - if (string.IsNullOrEmpty(name)) + if (OwnerCacheObject is CacheMember cacheMember) { - if (OwnerCacheObject is CacheMember cacheMember) - { - name = cacheMember.MemInfo.Name; - } - else - { - name = "UNTITLED"; - } + name = cacheMember.MemInfo.Name; + } + else + { + name = "UNTITLED"; } - - SaveTextureAsPNG(currentTex, saveFolder, name, false); - - ExplorerCore.Log($@"Saved to {saveFolder}\{name}.png!"); - } - else - { - ExplorerCore.Log("Cannot save a null texture!"); } + + Texture2DHelpers.SaveTextureAsPNG(currentTex, saveFolder, name, false); + + ExplorerCore.Log($@"Saved to {saveFolder}\{name}.png!"); } } @@ -148,107 +144,6 @@ namespace Explorer.UI return s; } - 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"; - - try - { - if (isDTXnmNormal) - { - tex = DTXnmToRGBA(tex); - tex.Apply(false, false); - } - - data = tex.EncodeToPNG(); - - if (data == null) - { - ExplorerCore.Log("Couldn't get data with EncodeToPNG (probably ReadOnly?), trying manually..."); - throw new Exception(); - } - } - catch - { - var origFilter = tex.filterMode; - tex.filterMode = FilterMode.Point; - - RenderTexture rt = RenderTexture.GetTemporary(tex.width, tex.height); - 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; - - data = _newTex.EncodeToPNG(); - //data = _newTex.GetRawTextureData(); - } - - 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; - } + } } diff --git a/src/UI/Main/SearchPage.cs b/src/UI/Main/SearchPage.cs index 5ca227d..153aa6e 100644 --- a/src/UI/Main/SearchPage.cs +++ b/src/UI/Main/SearchPage.cs @@ -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(); } }