Skip to content
This repository was archived by the owner on May 9, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 1 addition & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
21 changes: 21 additions & 0 deletions src/Core/Reflection/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,27 @@ public static HashSet<Type> GetImplementationsOf(this Type baseType, bool allowA

// ------- Misc extensions --------

/// <summary>
/// Recursively check the type and its base types to find any generic arguments.
/// </summary>
public static bool TryGetGenericArguments(this Type type, out Type[] args)
{
if (type.IsGenericType)
{
args = type.GetGenericArguments();
return true;
}
else if (type.BaseType != null)
{
return TryGetGenericArguments(type.BaseType, out args);
}
else
{
args = null;
return false;
}
}

/// <summary>
/// Safely try to get all Types inside an Assembly.
/// </summary>
Expand Down
32 changes: 32 additions & 0 deletions src/Core/Tests/TestClass.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,40 @@

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}");
Expand Down
2 changes: 1 addition & 1 deletion src/ExplorerCore.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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.1";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer";

Expand Down
9 changes: 5 additions & 4 deletions src/UI/IValues/InteractiveDictionary.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,11 @@ public override void SetValue(object value)
else
{
var type = value.GetActualType();
if (type.IsGenericType && type.GetGenericArguments().Length == 2)
{
KeyType = type.GetGenericArguments()[0];
ValueType = type.GetGenericArguments()[1];

if (type.TryGetGenericArguments(out var args) && args.Length == 2)
{
KeyType = args[0];
ValueType = args[1];
}
else
{
Expand Down
80 changes: 66 additions & 14 deletions src/UI/IValues/InteractiveList.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -19,13 +20,15 @@ public class InteractiveList : InteractiveValue, ICellPoolDataSource<CacheListEn
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; }
Expand All @@ -49,7 +52,6 @@ public override void ReleaseFromOwner()
private void ClearAndRelease()
{
RefIList = null;
values.Clear();

foreach (var entry in cachedEntries)
{
Expand All @@ -66,14 +68,14 @@ public override void SetValue(object value)
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];
if (type.TryGetGenericArguments(out var args))
EntryType = args[0];
else if (type.HasElementType)
EntryType = type.GetElementType();
else
Expand All @@ -92,7 +94,12 @@ private void CacheEntries(object value)
{
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))
Expand All @@ -103,8 +110,6 @@ private void CacheEntries(object value)
{
var entry = enumerator.Current;

values.Add(entry);

// If list count increased, create new cache entries
CacheListEntry cache;
if (idx >= cachedEntries.Count)
Expand All @@ -122,9 +127,9 @@ private void CacheEntries(object value)
}

// 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)
Expand All @@ -141,14 +146,61 @@ private void CacheEntries(object value)
}
}

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);
Expand Down
7 changes: 2 additions & 5 deletions src/UI/Inspectors/GameObjectInspector.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public override GameObject CreateContent(GameObject parent)

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);

Expand All @@ -244,10 +244,7 @@ private void ConstructLists()
{
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)

Expand Down
12 changes: 6 additions & 6 deletions src/UI/Inspectors/InspectUnderMouse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,8 @@ internal void UpdatePosition(Vector2 mousePos)
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;

Expand Down Expand Up @@ -341,10 +341,10 @@ private static void StopUIInspect()

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()
Expand Down
8 changes: 4 additions & 4 deletions src/UI/Panels/CSConsolePanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,10 @@ public override void DoSaveToConfigElement()

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()
Expand Down
14 changes: 7 additions & 7 deletions src/UI/Panels/InspectorPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ public class InspectorPanel : UIPanel
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()
{
Expand All @@ -38,7 +38,7 @@ public override void OnFinishResize(RectTransform panel)
{
base.OnFinishResize(panel);

InspectorManager.PanelWidth = this.mainPanelRect.rect.width;
InspectorManager.PanelWidth = this.Rect.rect.width;
InspectorManager.OnPanelResized(panel.rect.width);
}

Expand All @@ -51,10 +51,10 @@ public override void DoSaveToConfigElement()

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()
Expand Down
10 changes: 5 additions & 5 deletions src/UI/Panels/LogPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public override void SetActive(bool active)

if (active && !DoneScrollPoolInit)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(this.mainPanelRect);
LayoutRebuilder.ForceRebuildLayoutImmediate(this.Rect);
logScrollPool.Initialize(this);
DoneScrollPoolInit = true;
}
Expand Down Expand Up @@ -158,10 +158,10 @@ public override void DoSaveToConfigElement()

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
Expand Down
8 changes: 4 additions & 4 deletions src/UI/Panels/ObjectExplorerPanel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -99,10 +99,10 @@ public override void ApplySaveData(string data)

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);
}

Expand Down
Loading