Compare commits

..

16 Commits
4.0.1 ... 4.0.3

32 changed files with 458 additions and 249 deletions

View File

@ -20,7 +20,6 @@
| Standalone | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.Standalone.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.Standalone.Mono.zip) | | Standalone | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.Standalone.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.Standalone.Mono.zip) |
### Known issues ### Known issues
* UI layouts broken/unusable after changing resolutions: delete the file `data.ini` in the UnityExplorer folder (same place as where you put the DLL). Better fix being worked on.
* Any `MissingMethodException` or `NotSupportedException`: please report the issue and provide a copy of your mod loader log and/or Unity log. * Any `MissingMethodException` or `NotSupportedException`: please report the issue and provide a copy of your mod loader log and/or Unity log.
* The C# console may unexpectedly produce a GC Mark Overflow crash when calling certain outside methods. Not clear yet what is causing this, but it's being looked into. * The C# console may unexpectedly produce a GC Mark Overflow crash when calling certain outside methods. Not clear yet what is causing this, but it's being looked into.
* In IL2CPP, some IEnumerable and IDictionary types may fail enumeration. Waiting for the Unhollower rewrite to address this any further. * In IL2CPP, some IEnumerable and IDictionary types may fail enumeration. Waiting for the Unhollower rewrite to address this any further.
@ -95,8 +94,7 @@ Depending on the release you are using, the config file will be found at:
## Building ## Building
Building the project should be straight-forward, the references are all inside the `lib\` folder. 0. Clone the repository and run `git submodule update --init --recursive` to get the submodules.
1. Open the `src\UnityExplorer.sln` project in Visual Studio. 1. Open the `src\UnityExplorer.sln` project in Visual Studio.
2. Select `Solution 'UnityExplorer' (1 of 1 project)` in the Solution Explorer panel, and set the <b>Active config</b> property to the version you want to build, then build it. Alternatively, use "Batch Build" and select all releases. 2. Select `Solution 'UnityExplorer' (1 of 1 project)` in the Solution Explorer panel, and set the <b>Active config</b> property to the version you want to build, then build it. Alternatively, use "Batch Build" and select all releases.
3. The DLLs are built to the `Release\` folder in the root of the repository. 3. The DLLs are built to the `Release\` folder in the root of the repository.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -26,18 +26,16 @@ namespace UnityExplorer.Core.Input
public static bool ShouldActuallyUnlock => UIManager.ShowMenu && Unlock; public static bool ShouldActuallyUnlock => UIManager.ShowMenu && Unlock;
private static CursorLockMode m_lastLockMode; private static CursorLockMode lastLockMode;
private static bool m_lastVisibleState; private static bool lastVisibleState;
private static bool m_currentlySettingCursor = false; private static bool currentlySettingCursor = false;
private static Type CursorType
=> m_cursorType
?? (m_cursorType = ReflectionUtility.GetTypeByName("UnityEngine.Cursor"));
private static Type m_cursorType;
public static void Init() public static void Init()
{ {
lastLockMode = Cursor.lockState;
lastVisibleState = Cursor.visible;
SetupPatches(); SetupPatches();
UpdateCursorControl(); UpdateCursorControl();
@ -78,7 +76,7 @@ namespace UnityExplorer.Core.Input
{ {
try try
{ {
m_currentlySettingCursor = true; currentlySettingCursor = true;
if (ShouldActuallyUnlock) if (ShouldActuallyUnlock)
{ {
@ -90,14 +88,14 @@ namespace UnityExplorer.Core.Input
} }
else else
{ {
Cursor.lockState = m_lastLockMode; Cursor.lockState = lastLockMode;
Cursor.visible = m_lastVisibleState; Cursor.visible = lastVisibleState;
if (UIManager.EventSys) if (UIManager.EventSys)
ReleaseEventSystem(); ReleaseEventSystem();
} }
m_currentlySettingCursor = false; currentlySettingCursor = false;
} }
catch (Exception e) catch (Exception e)
{ {
@ -107,9 +105,9 @@ namespace UnityExplorer.Core.Input
// Event system overrides // Event system overrides
private static bool m_settingEventSystem; private static bool settingEventSystem;
private static EventSystem m_lastEventSystem; private static EventSystem lastEventSystem;
private static BaseInputModule m_lastInputModule; private static BaseInputModule lastInputModule;
public static void SetEventSystem() public static void SetEventSystem()
{ {
@ -118,16 +116,16 @@ namespace UnityExplorer.Core.Input
if (EventSystem.current && EventSystem.current != UIManager.EventSys) if (EventSystem.current && EventSystem.current != UIManager.EventSys)
{ {
m_lastEventSystem = EventSystem.current; lastEventSystem = EventSystem.current;
m_lastEventSystem.enabled = false; lastEventSystem.enabled = false;
} }
// Set to our current system // Set to our current system
m_settingEventSystem = true; settingEventSystem = true;
UIManager.EventSys.enabled = true; UIManager.EventSys.enabled = true;
EventSystem.current = UIManager.EventSys; EventSystem.current = UIManager.EventSys;
InputManager.ActivateUIModule(); InputManager.ActivateUIModule();
m_settingEventSystem = false; settingEventSystem = false;
} }
public static void ReleaseEventSystem() public static void ReleaseEventSystem()
@ -135,14 +133,14 @@ namespace UnityExplorer.Core.Input
if (InputManager.CurrentType == InputType.InputSystem) if (InputManager.CurrentType == InputType.InputSystem)
return; return;
if (m_lastEventSystem && m_lastEventSystem.gameObject.activeSelf) if (lastEventSystem && lastEventSystem.gameObject.activeSelf)
{ {
m_lastEventSystem.enabled = true; lastEventSystem.enabled = true;
m_settingEventSystem = true; settingEventSystem = true;
EventSystem.current = m_lastEventSystem; EventSystem.current = lastEventSystem;
m_lastInputModule?.ActivateModule(); lastInputModule?.ActivateModule();
m_settingEventSystem = false; settingEventSystem = false;
} }
} }
@ -152,30 +150,30 @@ namespace UnityExplorer.Core.Input
{ {
try try
{ {
if (CursorType == null)
throw new Exception("Could not load Type 'UnityEngine.Cursor'!");
// Get current cursor state and enable cursor
m_lastLockMode = (CursorLockMode?)CursorType.GetProperty("lockState", BF.Public | BF.Static)?.GetValue(null, null)
?? CursorLockMode.None;
m_lastVisibleState = (bool?)CursorType.GetProperty("visible", BF.Public | BF.Static)?.GetValue(null, null)
?? false;
ExplorerCore.Loader.SetupCursorPatches(); ExplorerCore.Loader.SetupCursorPatches();
} }
catch (Exception e) catch (Exception e)
{ {
ExplorerCore.Log($"Error on CursorUnlocker.Init! {e.GetType()}, {e.Message}"); ExplorerCore.Log($"Exception setting up Cursor patches: {e.GetType()}, {e.Message}");
} }
} }
public static void Prefix_EventSystem_set_current(ref EventSystem value) public static void Prefix_EventSystem_set_current(ref EventSystem value)
{ {
if (!m_settingEventSystem && value != UIManager.EventSys) if (!UIManager.EventSys)
{ {
m_lastEventSystem = value; if (value)
m_lastInputModule = value?.currentInputModule; {
lastEventSystem = value;
lastInputModule = value.currentInputModule;
}
return;
}
if (!settingEventSystem && value != UIManager.EventSys)
{
lastEventSystem = value;
lastInputModule = value?.currentInputModule;
if (ShouldActuallyUnlock) if (ShouldActuallyUnlock)
{ {
@ -191,9 +189,9 @@ namespace UnityExplorer.Core.Input
public static void Prefix_set_lockState(ref CursorLockMode value) public static void Prefix_set_lockState(ref CursorLockMode value)
{ {
if (!m_currentlySettingCursor) if (!currentlySettingCursor)
{ {
m_lastLockMode = value; lastLockMode = value;
if (ShouldActuallyUnlock) if (ShouldActuallyUnlock)
value = CursorLockMode.None; value = CursorLockMode.None;
@ -202,9 +200,9 @@ namespace UnityExplorer.Core.Input
public static void Prefix_set_visible(ref bool value) public static void Prefix_set_visible(ref bool value)
{ {
if (!m_currentlySettingCursor) if (!currentlySettingCursor)
{ {
m_lastVisibleState = value; lastVisibleState = value;
if (ShouldActuallyUnlock) if (ShouldActuallyUnlock)
value = true; value = true;

View File

@ -61,7 +61,7 @@ namespace UnityExplorer.Core.Input
// First, just try to use the legacy input, see if its working. // First, just try to use the legacy input, see if its working.
// The InputSystem package may be present but not actually activated, so we can find out this way. // The InputSystem package may be present but not actually activated, so we can find out this way.
if (LegacyInput.TInput != null || (ReflectionUtility.LoadModule("UnityEngine.InputLegacyModule") && LegacyInput.TInput != null)) if (LegacyInput.TInput != null)
{ {
try try
{ {
@ -80,7 +80,7 @@ namespace UnityExplorer.Core.Input
} }
} }
if (InputSystem.TKeyboard != null || (ReflectionUtility.LoadModule("Unity.InputSystem") && InputSystem.TKeyboard != null)) if (InputSystem.TKeyboard != null)
{ {
try try
{ {

View File

@ -15,6 +15,7 @@ using UnityExplorer.Core;
using CppType = Il2CppSystem.Type; using CppType = Il2CppSystem.Type;
using BF = System.Reflection.BindingFlags; using BF = System.Reflection.BindingFlags;
using UnityExplorer.Core.Config; using UnityExplorer.Core.Config;
using UnhollowerBaseLib.Attributes;
namespace UnityExplorer namespace UnityExplorer
{ {
@ -80,15 +81,19 @@ namespace UnityExplorer
{ {
try try
{ {
// Thanks to Slaynash for this if (!type.CustomAttributes.Any())
if (type.CustomAttributes.Any(it => it.AttributeType.Name == "ObfuscatedNameAttribute")) return;
{
var cppType = Il2CppType.From(type);
if (!DeobfuscatedTypes.ContainsKey(cppType.FullName)) foreach (var att in type.CustomAttributes)
{
// Thanks to Slaynash for this
if (att.AttributeType == typeof(ObfuscatedNameAttribute))
{ {
DeobfuscatedTypes.Add(cppType.FullName, type); string obfuscatedName = att.ConstructorArguments[0].Value.ToString();
reverseDeobCache.Add(type.FullName, cppType.FullName);
DeobfuscatedTypes.Add(obfuscatedName, type);
reverseDeobCache.Add(type.FullName, obfuscatedName);
} }
} }
} }
@ -258,6 +263,18 @@ namespace UnityExplorer
} }
} }
//private static bool IsAssignableFrom(Type thisType, Type fromType)
//{
// if (!Il2CppTypeNotNull(fromType, out IntPtr fromTypePtr)
// || !Il2CppTypeNotNull(thisType, out IntPtr thisTypePtr))
// {
// // one or both of the types are not Il2Cpp types, use normal check
// return thisType.IsAssignableFrom(fromType);
// }
//
// return il2cpp_class_is_assignable_from(thisTypePtr, fromTypePtr);
//}
#endregion #endregion
@ -438,42 +455,27 @@ namespace UnityExplorer
#region Force-loading game modules #region Force-loading game modules
// Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded. internal static string UnhollowedFolderPath => Path.GetFullPath(
internal override bool Internal_LoadModule(string moduleName)
{
if (!moduleName.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase))
moduleName += ".dll";
#if ML #if ML
var path = Path.Combine("MelonLoader", "Managed", $"{moduleName}"); Path.Combine("MelonLoader", "Managed")
#elif BIE
Path.Combine("BepInEx", "unhollowed")
#else #else
var path = Path.Combine("BepInEx", "unhollowed", $"{moduleName}"); Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Modules")
#endif #endif
return DoLoadModule(path); );
}
// Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded.
// Force loading all il2cpp modules // Force loading all il2cpp modules
internal void TryLoadGameModules() internal void TryLoadGameModules()
{ {
string dirpath = if (Directory.Exists(UnhollowedFolderPath))
#if ML
Path.Combine("MelonLoader", "Managed");
#elif BIE
Path.Combine("BepInEx", "unhollowed");
#else
Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Modules");
#endif
;
if (Directory.Exists(dirpath))
{ {
var files = Directory.GetFiles(dirpath); var files = Directory.GetFiles(UnhollowedFolderPath);
foreach (var filePath in files) foreach (var filePath in files)
{ {
var name = Path.GetFileName(filePath);
if (!name.StartsWith("Unity") && !name.StartsWith("Assembly-CSharp"))
continue;
try try
{ {
DoLoadModule(filePath, true); DoLoadModule(filePath, true);
@ -484,6 +486,8 @@ namespace UnityExplorer
} }
} }
} }
else
ExplorerCore.LogWarning($"Expected Unhollowed folder path does not exist: '{UnhollowedFolderPath}'");
} }
internal bool DoLoadModule(string fullPath, bool suppressWarning = false) internal bool DoLoadModule(string fullPath, bool suppressWarning = false)
@ -493,7 +497,8 @@ namespace UnityExplorer
try try
{ {
Assembly.Load(File.ReadAllBytes(fullPath)); Assembly.LoadFile(fullPath);
//Assembly.Load(File.ReadAllBytes(fullPath));
return true; return true;
} }
catch (Exception e) catch (Exception e)
@ -508,7 +513,7 @@ namespace UnityExplorer
#endregion #endregion
#region Il2cpp reflection blacklist #region Il2cpp reflection blacklist
public override string DefaultReflectionBlacklist => string.Join(";", defaultIl2CppBlacklist); public override string DefaultReflectionBlacklist => string.Join(";", defaultIl2CppBlacklist);
@ -636,6 +641,9 @@ namespace UnityExplorer
"UnityEngine.Scripting.GarbageCollector+CollectIncrementalDelegate.Invoke", "UnityEngine.Scripting.GarbageCollector+CollectIncrementalDelegate.Invoke",
"UnityEngine.Scripting.GarbageCollector.CollectIncremental", "UnityEngine.Scripting.GarbageCollector.CollectIncremental",
"UnityEngine.SpherecastCommand.ScheduleBatch", "UnityEngine.SpherecastCommand.ScheduleBatch",
"UnityEngine.Texture.GetPixelDataSize",
"UnityEngine.Texture.GetPixelDataOffset",
"UnityEngine.Texture.GetPixelDataOffset",
"UnityEngine.Texture2D+SetPixelDataImplArrayDelegate.Invoke", "UnityEngine.Texture2D+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Texture2D+SetPixelDataImplDelegate.Invoke", "UnityEngine.Texture2D+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Texture2D.SetPixelDataImpl", "UnityEngine.Texture2D.SetPixelDataImpl",
@ -654,10 +662,54 @@ namespace UnityExplorer
"UnityEngine.XR.InputDevice.SendHapticImpulse", "UnityEngine.XR.InputDevice.SendHapticImpulse",
}; };
#endregion #endregion
#region Temp il2cpp list/dictionary fixes #region IL2CPP IEnumerable and IDictionary
protected override bool Internal_TryGetEntryType(Type enumerableType, out Type type)
{
// Check for system types (not unhollowed)
if (base.Internal_TryGetEntryType(enumerableType, out type))
return true;
// Type is either an IL2CPP enumerable, or its not generic.
if (type.IsGenericType)
{
// Temporary naive solution until IL2CPP interface support improves.
// This will work fine for most cases, but there are edge cases which would not work.
type = type.GetGenericArguments()[0];
return true;
}
// Unable to determine entry type
type = typeof(object);
return false;
}
protected override bool Internal_TryGetEntryTypes(Type type, out Type keys, out Type values)
{
if (base.Internal_TryGetEntryTypes(type, out keys, out values))
return true;
// Type is either an IL2CPP dictionary, or its not generic.
if (type.IsGenericType)
{
// Naive solution until IL2CPP interfaces improve.
var args = type.GetGenericArguments();
if (args.Length == 2)
{
keys = args[0];
values = args[1];
return true;
}
}
keys = typeof(object);
values = typeof(object);
return false;
}
// Temp fix until Unhollower interface support improves // Temp fix until Unhollower interface support improves

View File

@ -151,12 +151,14 @@ namespace UnityExplorer
internal virtual string Internal_ProcessTypeInString(string theString, Type type) internal virtual string Internal_ProcessTypeInString(string theString, Type type)
=> theString; => theString;
// Force loading modules //// Force loading modules
public static bool LoadModule(string moduleName) //public static bool LoadModule(string moduleName)
=> Instance.Internal_LoadModule(moduleName); // => Instance.Internal_LoadModule(moduleName);
//
//internal virtual bool Internal_LoadModule(string moduleName)
// => false;
internal virtual bool Internal_LoadModule(string moduleName) // Singleton finder
=> false;
public static void FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances) public static void FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances)
=> Instance.Internal_FindSingleton(possibleNames, type, flags, instances); => Instance.Internal_FindSingleton(possibleNames, type, flags, instances);
@ -441,7 +443,6 @@ namespace UnityExplorer
blacklist = Instance.DefaultReflectionBlacklist; blacklist = Instance.DefaultReflectionBlacklist;
ConfigManager.Reflection_Signature_Blacklist.Value = blacklist; ConfigManager.Reflection_Signature_Blacklist.Value = blacklist;
ConfigManager.Handler.SaveConfig(); ConfigManager.Handler.SaveConfig();
return;
} }
if (string.IsNullOrEmpty(blacklist)) if (string.IsNullOrEmpty(blacklist))
@ -466,6 +467,7 @@ namespace UnityExplorer
return false; return false;
var sig = $"{member.DeclaringType.FullName}.{member.Name}"; var sig = $"{member.DeclaringType.FullName}.{member.Name}";
return currentBlacklist.Contains(sig); return currentBlacklist.Contains(sig);
} }
@ -495,6 +497,39 @@ namespace UnityExplorer
enumerator = (list as IEnumerable).GetEnumerator(); enumerator = (list as IEnumerable).GetEnumerator();
return true; return true;
} }
// TryGetEntryType
public static bool TryGetEntryType(Type enumerableType, out Type type)
=> Instance.Internal_TryGetEntryType(enumerableType, out type);
protected virtual bool Internal_TryGetEntryType(Type enumerableType, out Type type)
{
// Check for arrays
if (enumerableType.IsArray)
{
type = enumerableType.GetElementType();
return true;
}
// Check for implementation of IEnumerable<T>, IList<T> or ICollection<T>
foreach (var t in enumerableType.GetInterfaces())
{
if (t.IsGenericType)
{
var typeDef = t.GetGenericTypeDefinition();
if (typeDef == typeof(IEnumerable<>) || typeDef == typeof(IList<>) || typeDef == typeof(ICollection<>))
{
type = t.GetGenericArguments()[0];
return true;
}
}
}
// Unable to determine any generic element type, just use object.
type = typeof(object);
return false;
}
// IsDictionary // IsDictionary
@ -524,5 +559,28 @@ namespace UnityExplorer
yield return new DictionaryEntry(enumerator.Key, enumerator.Value); yield return new DictionaryEntry(enumerator.Key, enumerator.Value);
} }
} }
// TryGetEntryTypes
public static bool TryGetEntryTypes(Type dictionaryType, out Type keys, out Type values)
=> Instance.Internal_TryGetEntryTypes(dictionaryType, out keys, out values);
protected virtual bool Internal_TryGetEntryTypes(Type dictionaryType, out Type keys, out Type values)
{
foreach (var t in dictionaryType.GetInterfaces())
{
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
var args = t.GetGenericArguments();
keys = args[0];
values = args[1];
return true;
}
}
keys = typeof(object);
values = typeof(object);
return false;
}
} }
} }

View File

@ -22,7 +22,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
public override void Initialize() public override void Initialize()
{ {
ExplorerCore.Context = RuntimeContext.IL2CPP; ExplorerCore.Context = RuntimeContext.IL2CPP;
//Reflection = new Il2CppReflection();
TextureUtil = new Il2CppTextureUtil(); TextureUtil = new Il2CppTextureUtil();
} }
@ -30,19 +29,12 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
{ {
try try
{ {
//Application.add_logMessageReceived(new Action<string, string, LogType>(ExplorerCore.Instance.OnUnityLog)); Application.add_logMessageReceived(new Action<string, string, LogType>(Application_logMessageReceived));
var logType = ReflectionUtility.GetTypeByName("UnityEngine.Application+LogCallback");
var castMethod = logType.GetMethod("op_Implicit", new[] { typeof(Action<string, string, LogType>) });
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<string, string, LogType>(Application_logMessageReceived) })
});
} }
catch catch (Exception ex)
{ {
ExplorerCore.LogWarning("Exception setting up Unity log listener, make sure Unity libraries have been unstripped!"); ExplorerCore.LogWarning("Exception setting up Unity log listener, make sure Unity libraries have been unstripped!");
ExplorerCore.Log(ex);
} }
} }

View File

@ -14,8 +14,40 @@ using UnhollowerBaseLib;
namespace UnityExplorer.Tests namespace UnityExplorer.Tests
{ {
public class TestIndexer : IList<int>
{
private readonly List<int> list = new List<int>() { 1,2,3,4,5 };
public int Count => list.Count;
public bool IsReadOnly => false;
int IList<int>.this[int index]
{
get => list[index];
set => list[index] = value;
}
public int IndexOf(int item) => list.IndexOf(item);
public bool Contains(int item) => list.Contains(item);
public void Add(int item) => list.Add(item);
public void Insert(int index, int item) => list.Insert(index, item);
public bool Remove(int item) => list.Remove(item);
public void RemoveAt(int index) => list.RemoveAt(index);
public void Clear() => list.Clear();
public void CopyTo(int[] array, int arrayIndex) => list.CopyTo(array, arrayIndex);
public IEnumerator<int> GetEnumerator() => list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => list.GetEnumerator();
}
public static class TestClass public static class TestClass
{ {
public static readonly TestIndexer AAAAATest = new TestIndexer();
public static void ATestMethod(string s, float f, Vector3 vector, DateTime date, Quaternion quater, bool b, CameraClearFlags enumvalue) public static void ATestMethod(string s, float f, Vector3 vector, DateTime date, Quaternion quater, bool b, CameraClearFlags enumvalue)
{ {
ExplorerCore.Log($"{s}, {f}, {vector.ToString()}, {date}, {quater.eulerAngles.ToString()}, {b}, {enumvalue}"); ExplorerCore.Log($"{s}, {f}, {vector.ToString()}, {date}, {quater.eulerAngles.ToString()}, {b}, {enumvalue}");

View File

@ -19,7 +19,7 @@ namespace UnityExplorer
public static class ExplorerCore public static class ExplorerCore
{ {
public const string NAME = "UnityExplorer"; public const string NAME = "UnityExplorer";
public const string VERSION = "4.0.0"; public const string VERSION = "4.0.3";
public const string AUTHOR = "Sinai"; public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer"; public const string GUID = "com.sinai.unityexplorer";

View File

@ -246,7 +246,6 @@ namespace UnityExplorer.UI.CacheObject
var sig = GetSig(member); var sig = GetSig(member);
//ExplorerCore.Log($"Trying to cache member {sig}..."); //ExplorerCore.Log($"Trying to cache member {sig}...");
//ExplorerCore.Log(member.DeclaringType.FullName + "." + member.Name);
CacheMember cached; CacheMember cached;
Type returnType; Type returnType;
@ -324,7 +323,8 @@ namespace UnityExplorer.UI.CacheObject
} }
} }
internal static string GetSig(MemberInfo member) => $"{member.DeclaringType.Name}.{member.Name}"; internal static string GetSig(MemberInfo member)
=> $"{member.DeclaringType.Name}.{member.Name}";
internal static string GetArgumentString(ParameterInfo[] args) internal static string GetArgumentString(ParameterInfo[] args)
{ {

View File

@ -2,6 +2,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using UnityExplorer.UI.CacheObject; using UnityExplorer.UI.CacheObject;
@ -21,8 +22,8 @@ namespace UnityExplorer.UI.IValues
public override bool CanWrite => base.CanWrite && RefIDictionary != null && !RefIDictionary.IsReadOnly; public override bool CanWrite => base.CanWrite && RefIDictionary != null && !RefIDictionary.IsReadOnly;
public Type KeyType; public Type KeysType;
public Type ValueType; public Type ValuesType;
public IDictionary RefIDictionary; public IDictionary RefIDictionary;
public int ItemCount => cachedEntries.Count; public int ItemCount => cachedEntries.Count;
@ -75,23 +76,13 @@ namespace UnityExplorer.UI.IValues
else else
{ {
var type = value.GetActualType(); var type = value.GetActualType();
if (type.IsGenericType && type.GetGenericArguments().Length == 2) ReflectionUtility.TryGetEntryTypes(type, out KeysType, out ValuesType);
{
KeyType = type.GetGenericArguments()[0];
ValueType = type.GetGenericArguments()[1];
}
else
{
KeyType = typeof(object);
ValueType = typeof(object);
}
CacheEntries(value); CacheEntries(value);
TopLabel.text = $"[{cachedEntries.Count}] {SignatureHighlighter.Parse(type, false)}"; TopLabel.text = $"[{cachedEntries.Count}] {SignatureHighlighter.Parse(type, false)}";
} }
this.DictScrollPool.Refresh(true, false); this.DictScrollPool.Refresh(true, false);
} }
@ -116,7 +107,7 @@ namespace UnityExplorer.UI.IValues
else else
cache = cachedEntries[idx]; cache = cachedEntries[idx];
cache.SetFallbackType(ValueType); cache.SetFallbackType(ValuesType);
cache.SetKey(dictEnumerator.Current.Key); cache.SetKey(dictEnumerator.Current.Key);
cache.SetValueFromSource(dictEnumerator.Current.Value); cache.SetValueFromSource(dictEnumerator.Current.Value);

View File

@ -2,6 +2,7 @@
using System.Collections; using System.Collections;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Reflection;
using UnityEngine; using UnityEngine;
using UnityEngine.UI; using UnityEngine.UI;
using UnityExplorer.UI.CacheObject; using UnityExplorer.UI.CacheObject;
@ -19,13 +20,15 @@ namespace UnityExplorer.UI.IValues
object ICacheObjectController.Target => this.CurrentOwner.Value; object ICacheObjectController.Target => this.CurrentOwner.Value;
public Type TargetType { get; private set; } public Type TargetType { get; private set; }
public override bool CanWrite => base.CanWrite && RefIList != null && !RefIList.IsReadOnly; public override bool CanWrite => base.CanWrite && ((RefIList != null && !RefIList.IsReadOnly) || IsWritableGenericIList);
public Type EntryType; public Type EntryType;
public IList RefIList; public IList RefIList;
public int ItemCount => values.Count; private bool IsWritableGenericIList;
private readonly List<object> values = new List<object>(); private PropertyInfo genericIndexer;
public int ItemCount => cachedEntries.Count;
private readonly List<CacheListEntry> cachedEntries = new List<CacheListEntry>(); private readonly List<CacheListEntry> cachedEntries = new List<CacheListEntry>();
public ScrollPool<CacheListEntryCell> ListScrollPool { get; private set; } public ScrollPool<CacheListEntryCell> ListScrollPool { get; private set; }
@ -49,7 +52,6 @@ namespace UnityExplorer.UI.IValues
private void ClearAndRelease() private void ClearAndRelease()
{ {
RefIList = null; RefIList = null;
values.Clear();
foreach (var entry in cachedEntries) foreach (var entry in cachedEntries)
{ {
@ -66,18 +68,13 @@ namespace UnityExplorer.UI.IValues
if (value == null) if (value == null)
{ {
// should never be null // should never be null
if (values.Any()) if (cachedEntries.Any())
ClearAndRelease(); ClearAndRelease();
} }
else else
{ {
var type = value.GetActualType(); var type = value.GetActualType();
if (type.IsGenericType) ReflectionUtility.TryGetEntryType(type, out EntryType);
EntryType = type.GetGenericArguments()[0];
else if (type.HasElementType)
EntryType = type.GetElementType();
else
EntryType = typeof(object);
CacheEntries(value); CacheEntries(value);
@ -92,7 +89,12 @@ namespace UnityExplorer.UI.IValues
{ {
RefIList = value as IList; RefIList = value as IList;
values.Clear(); // Check if the type implements IList<T> but not IList (ie. Il2CppArrayBase)
if (RefIList == null)
CheckGenericIList(value);
else
IsWritableGenericIList = false;
int idx = 0; int idx = 0;
if (ReflectionUtility.TryGetEnumerator(value, out IEnumerator enumerator)) if (ReflectionUtility.TryGetEnumerator(value, out IEnumerator enumerator))
@ -103,8 +105,6 @@ namespace UnityExplorer.UI.IValues
{ {
var entry = enumerator.Current; var entry = enumerator.Current;
values.Add(entry);
// If list count increased, create new cache entries // If list count increased, create new cache entries
CacheListEntry cache; CacheListEntry cache;
if (idx >= cachedEntries.Count) if (idx >= cachedEntries.Count)
@ -122,9 +122,9 @@ namespace UnityExplorer.UI.IValues
} }
// Remove excess cached entries if list count decreased // Remove excess cached entries if list count decreased
if (cachedEntries.Count > values.Count) if (cachedEntries.Count > idx)
{ {
for (int i = cachedEntries.Count - 1; i >= values.Count; i--) for (int i = cachedEntries.Count - 1; i >= idx; i--)
{ {
var cache = cachedEntries[i]; var cache = cachedEntries[i];
if (cache.CellView != null) if (cache.CellView != null)
@ -141,14 +141,61 @@ namespace UnityExplorer.UI.IValues
} }
} }
private void CheckGenericIList(object value)
{
try
{
var type = value.GetType();
if (type.GetInterfaces().Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == typeof(IList<>)))
IsWritableGenericIList = !(bool)type.GetProperty("IsReadOnly").GetValue(value, null);
else
IsWritableGenericIList = false;
if (IsWritableGenericIList)
{
// Find the "this[int index]" property.
// It might be a private implementation.
foreach (var prop in type.GetProperties(ReflectionUtility.FLAGS))
{
if ((prop.Name == "Item"
|| (prop.Name.StartsWith("System.Collections.Generic.IList<") && prop.Name.EndsWith(">.Item")))
&& prop.GetIndexParameters() is ParameterInfo[] parameters
&& parameters.Length == 1
&& parameters[0].ParameterType == typeof(int))
{
genericIndexer = prop;
break;
}
}
if (genericIndexer == null)
{
ExplorerCore.LogWarning($"Failed to find indexer property for IList<T> type '{type.FullName}'!");
IsWritableGenericIList = false;
}
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception processing IEnumerable for IList<T> check: {ex.ReflectionExToString()}");
IsWritableGenericIList = false;
}
}
// Setting the value of an index to the list // Setting the value of an index to the list
public void TrySetValueToIndex(object value, int index) public void TrySetValueToIndex(object value, int index)
{ {
try try
{ {
//value = value.TryCast(this.EntryType); if (!IsWritableGenericIList)
RefIList[index] = value; {
RefIList[index] = value;
}
else
{
genericIndexer.SetValue(CurrentOwner.Value, value, new object[] { index });
}
var entry = cachedEntries[index]; var entry = cachedEntries[index];
entry.SetValueFromSource(value); entry.SetValueFromSource(value);

View File

@ -226,7 +226,7 @@ namespace UnityExplorer.UI.Inspectors
var scrollObj = UIFactory.CreateScrollView(UIRoot, "GameObjectInspector", out Content, out var scrollbar, var scrollObj = UIFactory.CreateScrollView(UIRoot, "GameObjectInspector", out Content, out var scrollbar,
new Color(0.065f, 0.065f, 0.065f)); new Color(0.065f, 0.065f, 0.065f));
UIFactory.SetLayoutElement(scrollObj, minHeight: 300, flexibleWidth: 9999, flexibleHeight: 1); UIFactory.SetLayoutElement(scrollObj, minHeight: 250, preferredHeight: 300, flexibleHeight: 0, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(Content, spacing: 3, padTop: 2, padBottom: 2, padLeft: 2, padRight: 2); UIFactory.SetLayoutGroup<VerticalLayoutGroup>(Content, spacing: 3, padTop: 2, padBottom: 2, padLeft: 2, padRight: 2);
@ -244,10 +244,7 @@ namespace UnityExplorer.UI.Inspectors
{ {
var listHolder = UIFactory.CreateUIObject("ListHolders", UIRoot); var listHolder = UIFactory.CreateUIObject("ListHolders", UIRoot);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(listHolder, false, true, true, true, 8, 2, 2, 2, 2); UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(listHolder, false, true, true, true, 8, 2, 2, 2, 2);
UIFactory.SetLayoutElement(listHolder, minHeight: 350, flexibleWidth: 9999, flexibleHeight: 9999); UIFactory.SetLayoutElement(listHolder, minHeight: 150, flexibleWidth: 9999, flexibleHeight: 9999);
//var listRect = listHolder.GetComponent<RectTransform>();
//listRect.anchorMin = new Vector2(0, 1);
//listRect.anchorMax = new Vector2(1, 1);
// Left group (Children) // Left group (Children)

View File

@ -155,8 +155,8 @@ namespace UnityExplorer.UI.Inspectors
mousePos.x = 350; mousePos.x = 350;
if (mousePos.x > Screen.width - 350) if (mousePos.x > Screen.width - 350)
mousePos.x = Screen.width - 350; mousePos.x = Screen.width - 350;
if (mousePos.y < mainPanelRect.rect.height) if (mousePos.y < Rect.rect.height)
mousePos.y += mainPanelRect.rect.height + 10; mousePos.y += Rect.rect.height + 10;
else else
mousePos.y -= 10; mousePos.y -= 10;
@ -341,10 +341,10 @@ namespace UnityExplorer.UI.Inspectors
protected internal override void DoSetDefaultPosAndAnchors() protected internal override void DoSetDefaultPosAndAnchors()
{ {
mainPanelRect.anchorMin = Vector2.zero; Rect.anchorMin = Vector2.zero;
mainPanelRect.anchorMax = Vector2.zero; Rect.anchorMax = Vector2.zero;
mainPanelRect.pivot = new Vector2(0.5f, 1); Rect.pivot = new Vector2(0.5f, 1);
mainPanelRect.sizeDelta = new Vector2(700, 150); Rect.sizeDelta = new Vector2(700, 150);
} }
public override void ConstructPanelContent() public override void ConstructPanelContent()

View File

@ -62,10 +62,10 @@ namespace UnityExplorer.UI.Panels
protected internal override void DoSetDefaultPosAndAnchors() protected internal override void DoSetDefaultPosAndAnchors()
{ {
mainPanelRect.localPosition = Vector2.zero; Rect.localPosition = Vector2.zero;
mainPanelRect.pivot = new Vector2(0f, 1f); Rect.pivot = new Vector2(0f, 1f);
mainPanelRect.anchorMin = new Vector2(0.4f, 0.175f); Rect.anchorMin = new Vector2(0.4f, 0.175f);
mainPanelRect.anchorMax = new Vector2(0.85f, 0.925f); Rect.anchorMax = new Vector2(0.85f, 0.925f);
} }
public override void ConstructPanelContent() public override void ConstructPanelContent()

View File

@ -26,8 +26,8 @@ namespace UnityExplorer.UI.Panels
public GameObject ContentHolder; public GameObject ContentHolder;
public RectTransform ContentRect; public RectTransform ContentRect;
public static float CurrentPanelWidth => Instance.mainPanelRect.rect.width; public static float CurrentPanelWidth => Instance.Rect.rect.width;
public static float CurrentPanelHeight => Instance.mainPanelRect.rect.height; public static float CurrentPanelHeight => Instance.Rect.rect.height;
public override void Update() public override void Update()
{ {
@ -38,7 +38,7 @@ namespace UnityExplorer.UI.Panels
{ {
base.OnFinishResize(panel); base.OnFinishResize(panel);
InspectorManager.PanelWidth = this.mainPanelRect.rect.width; InspectorManager.PanelWidth = this.Rect.rect.width;
InspectorManager.OnPanelResized(panel.rect.width); InspectorManager.OnPanelResized(panel.rect.width);
} }
@ -51,10 +51,10 @@ namespace UnityExplorer.UI.Panels
protected internal override void DoSetDefaultPosAndAnchors() protected internal override void DoSetDefaultPosAndAnchors()
{ {
mainPanelRect.localPosition = Vector2.zero; Rect.localPosition = Vector2.zero;
mainPanelRect.pivot = new Vector2(0f, 1f); Rect.pivot = new Vector2(0f, 1f);
mainPanelRect.anchorMin = new Vector2(0.35f, 0.175f); Rect.anchorMin = new Vector2(0.35f, 0.175f);
mainPanelRect.anchorMax = new Vector2(0.8f, 0.925f); Rect.anchorMax = new Vector2(0.8f, 0.925f);
} }
public override void ConstructPanelContent() public override void ConstructPanelContent()

View File

@ -51,7 +51,7 @@ namespace UnityExplorer.UI.Panels
if (active && !DoneScrollPoolInit) if (active && !DoneScrollPoolInit)
{ {
LayoutRebuilder.ForceRebuildLayoutImmediate(this.mainPanelRect); LayoutRebuilder.ForceRebuildLayoutImmediate(this.Rect);
logScrollPool.Initialize(this); logScrollPool.Initialize(this);
DoneScrollPoolInit = true; DoneScrollPoolInit = true;
} }
@ -158,10 +158,10 @@ namespace UnityExplorer.UI.Panels
protected internal override void DoSetDefaultPosAndAnchors() protected internal override void DoSetDefaultPosAndAnchors()
{ {
mainPanelRect.localPosition = Vector2.zero; Rect.localPosition = Vector2.zero;
mainPanelRect.pivot = new Vector2(0f, 1f); Rect.pivot = new Vector2(0f, 1f);
mainPanelRect.anchorMin = new Vector2(0.5f, 0.03f); Rect.anchorMin = new Vector2(0.5f, 0.03f);
mainPanelRect.anchorMax = new Vector2(0.9f, 0.2f); Rect.anchorMax = new Vector2(0.9f, 0.2f);
} }
// UI Construction // UI Construction

View File

@ -99,10 +99,10 @@ namespace UnityExplorer.UI.Panels
protected internal override void DoSetDefaultPosAndAnchors() protected internal override void DoSetDefaultPosAndAnchors()
{ {
mainPanelRect.localPosition = Vector2.zero; Rect.localPosition = Vector2.zero;
mainPanelRect.pivot = new Vector2(0f, 1f); Rect.pivot = new Vector2(0f, 1f);
mainPanelRect.anchorMin = new Vector2(0.125f, 0.175f); Rect.anchorMin = new Vector2(0.125f, 0.175f);
mainPanelRect.anchorMax = new Vector2(0.325f, 0.925f); Rect.anchorMax = new Vector2(0.325f, 0.925f);
//mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 350); //mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 350);
} }

View File

@ -69,11 +69,11 @@ namespace UnityExplorer.UI.Panels
protected internal override void DoSetDefaultPosAndAnchors() protected internal override void DoSetDefaultPosAndAnchors()
{ {
mainPanelRect.localPosition = Vector2.zero; Rect.localPosition = Vector2.zero;
mainPanelRect.pivot = new Vector2(0f, 1f); Rect.pivot = new Vector2(0f, 1f);
mainPanelRect.anchorMin = new Vector2(0.5f, 0.1f); Rect.anchorMin = new Vector2(0.5f, 0.1f);
mainPanelRect.anchorMax = new Vector2(0.5f, 0.85f); Rect.anchorMax = new Vector2(0.5f, 0.85f);
mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 600f); Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 600f);
} }
// UI Construction // UI Construction

View File

@ -250,15 +250,9 @@ namespace UnityExplorer.UI.Panels
Vector2 diff = (Vector2)mousePos - m_lastDragPosition; Vector2 diff = (Vector2)mousePos - m_lastDragPosition;
m_lastDragPosition = mousePos; m_lastDragPosition = mousePos;
var pos = Panel.localPosition + (Vector3)diff; Panel.localPosition = Panel.localPosition + (Vector3)diff;
// Prevent panel going oustide screen bounds UIPanel.EnsureValidPosition(Panel);
var halfW = Screen.width * 0.5f;
var halfH = Screen.height * 0.5f;
pos.x = Math.Max(-halfW, Math.Min(pos.x, halfW - Panel.rect.width));
pos.y = Math.Max(-halfH + Panel.rect.height, Math.Min(pos.y, halfH));
Panel.localPosition = pos;
} }
public void OnEndDrag() public void OnEndDrag()
@ -425,6 +419,9 @@ namespace UnityExplorer.UI.Panels
if ((Vector2)mousePos == m_lastResizePos) if ((Vector2)mousePos == m_lastResizePos)
return; return;
if (mousePos.x < 0 || mousePos.y < 0 || mousePos.x > Screen.width || mousePos.y > Screen.height)
return;
m_lastResizePos = mousePos; m_lastResizePos = mousePos;
float diffX = (float)((decimal)diff.x / Screen.width); float diffX = (float)((decimal)diff.x / Screen.width);

View File

@ -44,8 +44,8 @@ namespace UnityExplorer.UI.Panels
continue; continue;
// check if our mouse is clicking inside the panel // check if our mouse is clicking inside the panel
var pos = panel.mainPanelRect.InverseTransformPoint(mousePos); var pos = panel.Rect.InverseTransformPoint(mousePos);
if (!panel.Enabled || !panel.mainPanelRect.rect.Contains(pos)) if (!panel.Enabled || !panel.Rect.rect.Contains(pos))
continue; continue;
// if this is not the top panel, reorder and invoke the onchanged event // if this is not the top panel, reorder and invoke the onchanged event
@ -88,9 +88,11 @@ namespace UnityExplorer.UI.Panels
public override GameObject UIRoot => uiRoot; public override GameObject UIRoot => uiRoot;
protected GameObject uiRoot; protected GameObject uiRoot;
protected RectTransform mainPanelRect; public RectTransform Rect;
public GameObject content; public GameObject content;
public GameObject titleBar;
public abstract void ConstructPanelContent(); public abstract void ConstructPanelContent();
public virtual void OnFinishResize(RectTransform panel) public virtual void OnFinishResize(RectTransform panel)
@ -136,14 +138,85 @@ namespace UnityExplorer.UI.Panels
public void SetTransformDefaults() public void SetTransformDefaults()
{ {
DoSetDefaultPosAndAnchors(); DoSetDefaultPosAndAnchors();
if (mainPanelRect.rect.width < MinWidth)
mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth);
if (mainPanelRect.rect.height < MinHeight)
mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight);
} }
public GameObject titleBar; public void EnsureValidSize()
{
if (Rect.rect.width < MinWidth)
Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth);
if (Rect.rect.height < MinHeight)
Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight);
}
public static void EnsureValidPosition(RectTransform panel)
{
var pos = panel.localPosition;
// Prevent panel going oustide screen bounds
var halfW = Screen.width * 0.5f;
var halfH = Screen.height * 0.5f;
pos.x = Math.Max(-halfW, Math.Min(pos.x, halfW - panel.rect.width));
pos.y = Math.Max(-halfH + panel.rect.height, Math.Min(pos.y, halfH));
panel.localPosition = pos;
}
#region Save Data
public abstract void DoSaveToConfigElement();
public void SaveToConfigManager()
{
if (UIManager.Initializing)
return;
DoSaveToConfigElement();
}
public abstract string GetSaveDataFromConfigManager();
public bool ApplyingSaveData { get; set; }
public virtual string ToSaveData()
{
try
{
return $"{ShouldSaveActiveState && Enabled}" +
$"|{Rect.RectAnchorsToString()}" +
$"|{Rect.RectPositionToString()}";
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception generating Panel save data: {ex}");
return "";
}
}
public virtual void ApplySaveData(string data)
{
if (string.IsNullOrEmpty(data))
return;
var split = data.Split('|');
try
{
Rect.SetAnchorsFromString(split[1]);
Rect.SetPositionFromString(split[2]);
UIManager.SetPanelActive(this.PanelType, bool.Parse(split[0]));
}
catch
{
ExplorerCore.LogWarning("Invalid or corrupt panel save data! Restoring to default.");
SetTransformDefaults();
}
}
#endregion
// UI Construction
public void ConstructUI() public void ConstructUI()
{ {
@ -168,7 +241,7 @@ namespace UnityExplorer.UI.Panels
// create core canvas // create core canvas
uiRoot = UIFactory.CreatePanel(Name, out GameObject panelContent); uiRoot = UIFactory.CreatePanel(Name, out GameObject panelContent);
mainPanelRect = this.uiRoot.GetComponent<RectTransform>(); Rect = this.uiRoot.GetComponent<RectTransform>();
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(this.uiRoot, false, false, true, true, 0, 2, 2, 2, 2, TextAnchor.UpperLeft); UIFactory.SetLayoutGroup<VerticalLayoutGroup>(this.uiRoot, false, false, true, true, 0, 2, 2, 2, 2, TextAnchor.UpperLeft);
int id = this.uiRoot.transform.GetInstanceID(); int id = this.uiRoot.transform.GetInstanceID();
@ -177,9 +250,6 @@ namespace UnityExplorer.UI.Panels
content = panelContent; content = panelContent;
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(this.content, false, false, true, true, 2, 2, 2, 2, 2, TextAnchor.UpperLeft); UIFactory.SetLayoutGroup<VerticalLayoutGroup>(this.content, false, false, true, true, 2, 2, 2, 2, 2, TextAnchor.UpperLeft);
// always apply default pos and anchors (save data may only be partial)
SetTransformDefaults();
// Title bar // Title bar
titleBar = UIFactory.CreateHorizontalGroup(content, "TitleBar", false, true, true, true, 2, titleBar = UIFactory.CreateHorizontalGroup(content, "TitleBar", false, true, true, true, 2,
new Vector4(2, 2, 2, 2), new Color(0.06f, 0.06f, 0.06f)); new Vector4(2, 2, 2, 2), new Color(0.06f, 0.06f, 0.06f));
@ -210,7 +280,7 @@ namespace UnityExplorer.UI.Panels
// Panel dragger // Panel dragger
Dragger = new PanelDragger(titleBar.GetComponent<RectTransform>(), mainPanelRect, this); Dragger = new PanelDragger(titleBar.GetComponent<RectTransform>(), Rect, this);
Dragger.OnFinishResize += OnFinishResize; Dragger.OnFinishResize += OnFinishResize;
Dragger.OnFinishDrag += OnFinishDrag; Dragger.OnFinishDrag += OnFinishDrag;
@ -223,6 +293,7 @@ namespace UnityExplorer.UI.Panels
UIManager.SetPanelActive(this.PanelType, ShowByDefault); UIManager.SetPanelActive(this.PanelType, ShowByDefault);
ApplyingSaveData = true; ApplyingSaveData = true;
SetTransformDefaults();
// apply panel save data or revert to default // apply panel save data or revert to default
try try
{ {
@ -234,6 +305,13 @@ namespace UnityExplorer.UI.Panels
SetTransformDefaults(); SetTransformDefaults();
} }
LayoutRebuilder.ForceRebuildLayoutImmediate(this.Rect);
// ensure initialized position is valid
EnsureValidSize();
EnsureValidPosition(this.Rect);
// update dragger and save data
Dragger.OnEndResize(); Dragger.OnEndResize();
// simple listener for saving enabled state // simple listener for saving enabled state
@ -241,61 +319,11 @@ namespace UnityExplorer.UI.Panels
{ {
SaveToConfigManager(); SaveToConfigManager();
}; };
ApplyingSaveData = false; ApplyingSaveData = false;
} }
public override void ConstructUI(GameObject parent) => ConstructUI(); public override void ConstructUI(GameObject parent) => ConstructUI();
// SAVE DATA
public abstract void DoSaveToConfigElement();
public void SaveToConfigManager()
{
if (UIManager.Initializing)
return;
DoSaveToConfigElement();
}
public abstract string GetSaveDataFromConfigManager();
public bool ApplyingSaveData { get; set; }
public virtual string ToSaveData()
{
try
{
return $"{ShouldSaveActiveState && Enabled}" +
$"|{mainPanelRect.RectAnchorsToString()}" +
$"|{mainPanelRect.RectPositionToString()}";
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception generating Panel save data: {ex}");
return "";
}
}
public virtual void ApplySaveData(string data)
{
if (string.IsNullOrEmpty(data))
return;
var split = data.Split('|');
try
{
mainPanelRect.SetAnchorsFromString(split[1]);
mainPanelRect.SetPositionFromString(split[2]);
UIManager.SetPanelActive(this.PanelType, bool.Parse(split[0]));
}
catch
{
ExplorerCore.LogWarning("Invalid or corrupt panel save data! Restoring to default.");
SetTransformDefaults();
}
}
} }
#region WINDOW ANCHORS / POSITION HELPERS #region WINDOW ANCHORS / POSITION HELPERS

View File

@ -123,6 +123,9 @@ namespace UnityExplorer.UI
// Main UI Update loop // Main UI Update loop
private static int lastScreenWidth;
private static int lastScreenHeight;
public static void Update() public static void Update()
{ {
if (!CanvasRoot || Initializing) if (!CanvasRoot || Initializing)
@ -150,6 +153,19 @@ namespace UnityExplorer.UI
PanelDragger.UpdateInstances(); PanelDragger.UpdateInstances();
InputFieldRef.UpdateInstances(); InputFieldRef.UpdateInstances();
UIBehaviourModel.UpdateInstances(); UIBehaviourModel.UpdateInstances();
if (Screen.width != lastScreenWidth || Screen.height != lastScreenHeight)
{
lastScreenWidth = Screen.width;
lastScreenHeight = Screen.height;
foreach (var panel in UIPanels)
{
panel.Value.EnsureValidSize();
UIPanel.EnsureValidPosition(panel.Value.Rect);
panel.Value.Dragger.OnEndResize();
}
}
} }
// Initialization and UI Construction // Initialization and UI Construction
@ -184,6 +200,9 @@ namespace UnityExplorer.UI
ShowMenu = !ConfigManager.Hide_On_Startup.Value; ShowMenu = !ConfigManager.Hide_On_Startup.Value;
lastScreenWidth = Screen.width;
lastScreenHeight = Screen.height;
Initializing = false; Initializing = false;
} }

View File

@ -292,9 +292,9 @@ namespace UnityExplorer.UI.Widgets.AutoComplete
protected internal override void DoSetDefaultPosAndAnchors() protected internal override void DoSetDefaultPosAndAnchors()
{ {
mainPanelRect.pivot = new Vector2(0f, 1f); Rect.pivot = new Vector2(0f, 1f);
mainPanelRect.anchorMin = new Vector2(0.42f, 0.4f); Rect.anchorMin = new Vector2(0.42f, 0.4f);
mainPanelRect.anchorMax = new Vector2(0.68f, 0.6f); Rect.anchorMax = new Vector2(0.68f, 0.6f);
} }
public override void ConstructPanelContent() public override void ConstructPanelContent()

View File

@ -298,7 +298,7 @@ namespace UnityExplorer.UI.Widgets
if (CellPool.Count > 1) if (CellPool.Count > 1)
{ {
int index = CellPool.Count - 1 - (topPoolIndex % (CellPool.Count - 1)); int index = CellPool.Count - 1 - (topPoolIndex % (CellPool.Count - 1));
cell.Rect.SetSiblingIndex(index); cell.Rect.SetSiblingIndex(index + 1);
if (bottomPoolIndex == index - 1) if (bottomPoolIndex == index - 1)
bottomPoolIndex++; bottomPoolIndex++;