Compare commits

...

16 Commits
4.0.0 ... 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) |
### 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.
* 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.
@ -95,8 +94,7 @@ Depending on the release you are using, the config file will be found at:
## 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.
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.

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;
private static CursorLockMode m_lastLockMode;
private static bool m_lastVisibleState;
private static CursorLockMode lastLockMode;
private static bool lastVisibleState;
private static bool m_currentlySettingCursor = false;
private static Type CursorType
=> m_cursorType
?? (m_cursorType = ReflectionUtility.GetTypeByName("UnityEngine.Cursor"));
private static Type m_cursorType;
private static bool currentlySettingCursor = false;
public static void Init()
{
lastLockMode = Cursor.lockState;
lastVisibleState = Cursor.visible;
SetupPatches();
UpdateCursorControl();
@ -78,7 +76,7 @@ namespace UnityExplorer.Core.Input
{
try
{
m_currentlySettingCursor = true;
currentlySettingCursor = true;
if (ShouldActuallyUnlock)
{
@ -90,14 +88,14 @@ namespace UnityExplorer.Core.Input
}
else
{
Cursor.lockState = m_lastLockMode;
Cursor.visible = m_lastVisibleState;
Cursor.lockState = lastLockMode;
Cursor.visible = lastVisibleState;
if (UIManager.EventSys)
ReleaseEventSystem();
}
m_currentlySettingCursor = false;
currentlySettingCursor = false;
}
catch (Exception e)
{
@ -107,9 +105,9 @@ namespace UnityExplorer.Core.Input
// Event system overrides
private static bool m_settingEventSystem;
private static EventSystem m_lastEventSystem;
private static BaseInputModule m_lastInputModule;
private static bool settingEventSystem;
private static EventSystem lastEventSystem;
private static BaseInputModule lastInputModule;
public static void SetEventSystem()
{
@ -118,16 +116,16 @@ namespace UnityExplorer.Core.Input
if (EventSystem.current && EventSystem.current != UIManager.EventSys)
{
m_lastEventSystem = EventSystem.current;
m_lastEventSystem.enabled = false;
lastEventSystem = EventSystem.current;
lastEventSystem.enabled = false;
}
// Set to our current system
m_settingEventSystem = true;
settingEventSystem = true;
UIManager.EventSys.enabled = true;
EventSystem.current = UIManager.EventSys;
InputManager.ActivateUIModule();
m_settingEventSystem = false;
settingEventSystem = false;
}
public static void ReleaseEventSystem()
@ -135,14 +133,14 @@ namespace UnityExplorer.Core.Input
if (InputManager.CurrentType == InputType.InputSystem)
return;
if (m_lastEventSystem && m_lastEventSystem.gameObject.activeSelf)
if (lastEventSystem && lastEventSystem.gameObject.activeSelf)
{
m_lastEventSystem.enabled = true;
lastEventSystem.enabled = true;
m_settingEventSystem = true;
EventSystem.current = m_lastEventSystem;
m_lastInputModule?.ActivateModule();
m_settingEventSystem = false;
settingEventSystem = true;
EventSystem.current = lastEventSystem;
lastInputModule?.ActivateModule();
settingEventSystem = false;
}
}
@ -152,30 +150,30 @@ namespace UnityExplorer.Core.Input
{
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();
}
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)
{
if (!m_settingEventSystem && value != UIManager.EventSys)
if (!UIManager.EventSys)
{
m_lastEventSystem = value;
m_lastInputModule = value?.currentInputModule;
if (value)
{
lastEventSystem = value;
lastInputModule = value.currentInputModule;
}
return;
}
if (!settingEventSystem && value != UIManager.EventSys)
{
lastEventSystem = value;
lastInputModule = value?.currentInputModule;
if (ShouldActuallyUnlock)
{
@ -191,9 +189,9 @@ namespace UnityExplorer.Core.Input
public static void Prefix_set_lockState(ref CursorLockMode value)
{
if (!m_currentlySettingCursor)
if (!currentlySettingCursor)
{
m_lastLockMode = value;
lastLockMode = value;
if (ShouldActuallyUnlock)
value = CursorLockMode.None;
@ -202,9 +200,9 @@ namespace UnityExplorer.Core.Input
public static void Prefix_set_visible(ref bool value)
{
if (!m_currentlySettingCursor)
if (!currentlySettingCursor)
{
m_lastVisibleState = value;
lastVisibleState = value;
if (ShouldActuallyUnlock)
value = true;

View File

@ -61,7 +61,7 @@ namespace UnityExplorer.Core.Input
// 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.
if (LegacyInput.TInput != null || (ReflectionUtility.LoadModule("UnityEngine.InputLegacyModule") && LegacyInput.TInput != null))
if (LegacyInput.TInput != null)
{
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
{

View File

@ -15,6 +15,7 @@ using UnityExplorer.Core;
using CppType = Il2CppSystem.Type;
using BF = System.Reflection.BindingFlags;
using UnityExplorer.Core.Config;
using UnhollowerBaseLib.Attributes;
namespace UnityExplorer
{
@ -80,15 +81,19 @@ namespace UnityExplorer
{
try
{
// Thanks to Slaynash for this
if (type.CustomAttributes.Any(it => it.AttributeType.Name == "ObfuscatedNameAttribute"))
{
var cppType = Il2CppType.From(type);
if (!type.CustomAttributes.Any())
return;
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);
reverseDeobCache.Add(type.FullName, cppType.FullName);
string obfuscatedName = att.ConstructorArguments[0].Value.ToString();
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
@ -438,42 +455,27 @@ namespace UnityExplorer
#region Force-loading game modules
// Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded.
internal override bool Internal_LoadModule(string moduleName)
{
if (!moduleName.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase))
moduleName += ".dll";
internal static string UnhollowedFolderPath => Path.GetFullPath(
#if ML
var path = Path.Combine("MelonLoader", "Managed", $"{moduleName}");
Path.Combine("MelonLoader", "Managed")
#elif BIE
Path.Combine("BepInEx", "unhollowed")
#else
var path = Path.Combine("BepInEx", "unhollowed", $"{moduleName}");
Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Modules")
#endif
return DoLoadModule(path);
}
);
// Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded.
// Force loading all il2cpp modules
internal void TryLoadGameModules()
{
string dirpath =
#if ML
Path.Combine("MelonLoader", "Managed");
#elif BIE
Path.Combine("BepInEx", "unhollowed");
#else
Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Modules");
#endif
;
if (Directory.Exists(dirpath))
if (Directory.Exists(UnhollowedFolderPath))
{
var files = Directory.GetFiles(dirpath);
var files = Directory.GetFiles(UnhollowedFolderPath);
foreach (var filePath in files)
{
var name = Path.GetFileName(filePath);
if (!name.StartsWith("Unity") && !name.StartsWith("Assembly-CSharp"))
continue;
try
{
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)
@ -493,7 +497,8 @@ namespace UnityExplorer
try
{
Assembly.Load(File.ReadAllBytes(fullPath));
Assembly.LoadFile(fullPath);
//Assembly.Load(File.ReadAllBytes(fullPath));
return true;
}
catch (Exception e)
@ -508,7 +513,7 @@ namespace UnityExplorer
#endregion
#region Il2cpp reflection blacklist
#region Il2cpp reflection blacklist
public override string DefaultReflectionBlacklist => string.Join(";", defaultIl2CppBlacklist);
@ -636,6 +641,9 @@ namespace UnityExplorer
"UnityEngine.Scripting.GarbageCollector+CollectIncrementalDelegate.Invoke",
"UnityEngine.Scripting.GarbageCollector.CollectIncremental",
"UnityEngine.SpherecastCommand.ScheduleBatch",
"UnityEngine.Texture.GetPixelDataSize",
"UnityEngine.Texture.GetPixelDataOffset",
"UnityEngine.Texture.GetPixelDataOffset",
"UnityEngine.Texture2D+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Texture2D+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Texture2D.SetPixelDataImpl",
@ -654,10 +662,54 @@ namespace UnityExplorer
"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

View File

@ -151,12 +151,14 @@ namespace UnityExplorer
internal virtual string Internal_ProcessTypeInString(string theString, Type type)
=> theString;
// Force loading modules
public static bool LoadModule(string moduleName)
=> Instance.Internal_LoadModule(moduleName);
//// Force loading modules
//public static bool LoadModule(string moduleName)
// => Instance.Internal_LoadModule(moduleName);
//
//internal virtual bool Internal_LoadModule(string moduleName)
// => false;
internal virtual bool Internal_LoadModule(string moduleName)
=> false;
// Singleton finder
public static void FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances)
=> Instance.Internal_FindSingleton(possibleNames, type, flags, instances);
@ -441,7 +443,6 @@ namespace UnityExplorer
blacklist = Instance.DefaultReflectionBlacklist;
ConfigManager.Reflection_Signature_Blacklist.Value = blacklist;
ConfigManager.Handler.SaveConfig();
return;
}
if (string.IsNullOrEmpty(blacklist))
@ -466,6 +467,7 @@ namespace UnityExplorer
return false;
var sig = $"{member.DeclaringType.FullName}.{member.Name}";
return currentBlacklist.Contains(sig);
}
@ -495,6 +497,39 @@ namespace UnityExplorer
enumerator = (list as IEnumerable).GetEnumerator();
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
@ -524,5 +559,28 @@ namespace UnityExplorer
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()
{
ExplorerCore.Context = RuntimeContext.IL2CPP;
//Reflection = new Il2CppReflection();
TextureUtil = new Il2CppTextureUtil();
}
@ -30,19 +29,12 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
{
try
{
//Application.add_logMessageReceived(new Action<string, string, LogType>(ExplorerCore.Instance.OnUnityLog));
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) })
});
Application.add_logMessageReceived(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.Log(ex);
}
}

View File

@ -14,8 +14,40 @@ using UnhollowerBaseLib;
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 readonly TestIndexer AAAAATest = new TestIndexer();
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}");

View File

@ -19,7 +19,7 @@ namespace UnityExplorer
public static class ExplorerCore
{
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 GUID = "com.sinai.unityexplorer";

View File

@ -246,7 +246,6 @@ namespace UnityExplorer.UI.CacheObject
var sig = GetSig(member);
//ExplorerCore.Log($"Trying to cache member {sig}...");
//ExplorerCore.Log(member.DeclaringType.FullName + "." + member.Name);
CacheMember cached;
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)
{

View File

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

View File

@ -2,6 +2,7 @@
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.CacheObject;
@ -19,13 +20,15 @@ namespace UnityExplorer.UI.IValues
object ICacheObjectController.Target => this.CurrentOwner.Value;
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 IList RefIList;
public int ItemCount => values.Count;
private readonly List<object> values = new List<object>();
private bool IsWritableGenericIList;
private PropertyInfo genericIndexer;
public int ItemCount => cachedEntries.Count;
private readonly List<CacheListEntry> cachedEntries = new List<CacheListEntry>();
public ScrollPool<CacheListEntryCell> ListScrollPool { get; private set; }
@ -49,7 +52,6 @@ namespace UnityExplorer.UI.IValues
private void ClearAndRelease()
{
RefIList = null;
values.Clear();
foreach (var entry in cachedEntries)
{
@ -66,18 +68,13 @@ namespace UnityExplorer.UI.IValues
if (value == null)
{
// should never be null
if (values.Any())
if (cachedEntries.Any())
ClearAndRelease();
}
else
{
var type = value.GetActualType();
if (type.IsGenericType)
EntryType = type.GetGenericArguments()[0];
else if (type.HasElementType)
EntryType = type.GetElementType();
else
EntryType = typeof(object);
ReflectionUtility.TryGetEntryType(type, out EntryType);
CacheEntries(value);
@ -92,7 +89,12 @@ namespace UnityExplorer.UI.IValues
{
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;
if (ReflectionUtility.TryGetEnumerator(value, out IEnumerator enumerator))
@ -103,8 +105,6 @@ namespace UnityExplorer.UI.IValues
{
var entry = enumerator.Current;
values.Add(entry);
// If list count increased, create new cache entries
CacheListEntry cache;
if (idx >= cachedEntries.Count)
@ -122,9 +122,9 @@ namespace UnityExplorer.UI.IValues
}
// 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];
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
public void TrySetValueToIndex(object value, int index)
{
try
{
//value = value.TryCast(this.EntryType);
RefIList[index] = value;
if (!IsWritableGenericIList)
{
RefIList[index] = value;
}
else
{
genericIndexer.SetValue(CurrentOwner.Value, value, new object[] { index });
}
var entry = cachedEntries[index];
entry.SetValueFromSource(value);

View File

@ -226,7 +226,7 @@ namespace UnityExplorer.UI.Inspectors
var scrollObj = UIFactory.CreateScrollView(UIRoot, "GameObjectInspector", out Content, out var scrollbar,
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);
@ -244,10 +244,7 @@ namespace UnityExplorer.UI.Inspectors
{
var listHolder = UIFactory.CreateUIObject("ListHolders", UIRoot);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(listHolder, false, true, true, true, 8, 2, 2, 2, 2);
UIFactory.SetLayoutElement(listHolder, minHeight: 350, flexibleWidth: 9999, flexibleHeight: 9999);
//var listRect = listHolder.GetComponent<RectTransform>();
//listRect.anchorMin = new Vector2(0, 1);
//listRect.anchorMax = new Vector2(1, 1);
UIFactory.SetLayoutElement(listHolder, minHeight: 150, flexibleWidth: 9999, flexibleHeight: 9999);
// Left group (Children)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -250,15 +250,9 @@ namespace UnityExplorer.UI.Panels
Vector2 diff = (Vector2)mousePos - m_lastDragPosition;
m_lastDragPosition = mousePos;
var pos = Panel.localPosition + (Vector3)diff;
Panel.localPosition = Panel.localPosition + (Vector3)diff;
// 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;
UIPanel.EnsureValidPosition(Panel);
}
public void OnEndDrag()
@ -425,6 +419,9 @@ namespace UnityExplorer.UI.Panels
if ((Vector2)mousePos == m_lastResizePos)
return;
if (mousePos.x < 0 || mousePos.y < 0 || mousePos.x > Screen.width || mousePos.y > Screen.height)
return;
m_lastResizePos = mousePos;
float diffX = (float)((decimal)diff.x / Screen.width);

View File

@ -44,8 +44,8 @@ namespace UnityExplorer.UI.Panels
continue;
// check if our mouse is clicking inside the panel
var pos = panel.mainPanelRect.InverseTransformPoint(mousePos);
if (!panel.Enabled || !panel.mainPanelRect.rect.Contains(pos))
var pos = panel.Rect.InverseTransformPoint(mousePos);
if (!panel.Enabled || !panel.Rect.rect.Contains(pos))
continue;
// 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;
protected GameObject uiRoot;
protected RectTransform mainPanelRect;
public RectTransform Rect;
public GameObject content;
public GameObject titleBar;
public abstract void ConstructPanelContent();
public virtual void OnFinishResize(RectTransform panel)
@ -136,14 +138,85 @@ namespace UnityExplorer.UI.Panels
public void SetTransformDefaults()
{
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()
{
@ -168,7 +241,7 @@ namespace UnityExplorer.UI.Panels
// create core canvas
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);
int id = this.uiRoot.transform.GetInstanceID();
@ -177,9 +250,6 @@ namespace UnityExplorer.UI.Panels
content = panelContent;
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
titleBar = UIFactory.CreateHorizontalGroup(content, "TitleBar", false, true, true, true, 2,
new Vector4(2, 2, 2, 2), new Color(0.06f, 0.06f, 0.06f));
@ -210,7 +280,7 @@ namespace UnityExplorer.UI.Panels
// Panel dragger
Dragger = new PanelDragger(titleBar.GetComponent<RectTransform>(), mainPanelRect, this);
Dragger = new PanelDragger(titleBar.GetComponent<RectTransform>(), Rect, this);
Dragger.OnFinishResize += OnFinishResize;
Dragger.OnFinishDrag += OnFinishDrag;
@ -223,6 +293,7 @@ namespace UnityExplorer.UI.Panels
UIManager.SetPanelActive(this.PanelType, ShowByDefault);
ApplyingSaveData = true;
SetTransformDefaults();
// apply panel save data or revert to default
try
{
@ -234,6 +305,13 @@ namespace UnityExplorer.UI.Panels
SetTransformDefaults();
}
LayoutRebuilder.ForceRebuildLayoutImmediate(this.Rect);
// ensure initialized position is valid
EnsureValidSize();
EnsureValidPosition(this.Rect);
// update dragger and save data
Dragger.OnEndResize();
// simple listener for saving enabled state
@ -241,61 +319,11 @@ namespace UnityExplorer.UI.Panels
{
SaveToConfigManager();
};
ApplyingSaveData = false;
}
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

View File

@ -123,6 +123,9 @@ namespace UnityExplorer.UI
// Main UI Update loop
private static int lastScreenWidth;
private static int lastScreenHeight;
public static void Update()
{
if (!CanvasRoot || Initializing)
@ -150,6 +153,19 @@ namespace UnityExplorer.UI
PanelDragger.UpdateInstances();
InputFieldRef.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
@ -184,6 +200,9 @@ namespace UnityExplorer.UI
ShowMenu = !ConfigManager.Hide_On_Startup.Value;
lastScreenWidth = Screen.width;
lastScreenHeight = Screen.height;
Initializing = false;
}

View File

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

View File

@ -298,7 +298,7 @@ namespace UnityExplorer.UI.Widgets
if (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)
bottomPoolIndex++;