Compare commits

..

10 Commits
3.0.4 ... 3.0.7

18 changed files with 317 additions and 226 deletions

View File

@ -64,7 +64,7 @@
## Mod Config ## Mod Config
You can access the settings via the "Options" page of the main menu, or directly from the config at `Mods\UnityExplorer\config.xml` (generated after first launch). You can access the settings via the "Options" page of the main menu, or directly from the config at `Mods\UnityExplorer\config.ini` (generated after first launch).
`Main Menu Toggle` (KeyCode) `Main Menu Toggle` (KeyCode)
* Default: `F7` * Default: `F7`
@ -80,7 +80,7 @@ You can access the settings via the "Options" page of the main menu, or directly
* <b>Requires a restart to take effect</b>, apart from Reflection Inspector tabs. * <b>Requires a restart to take effect</b>, apart from Reflection Inspector tabs.
`Default Output Path` (string) `Default Output Path` (string)
* Default: `Mods\Explorer` * Default: `Mods\UnityExplorer`
* Where output is generated to, by default (for Texture PNG saving, etc). * Where output is generated to, by default (for Texture PNG saving, etc).
* Currently this is not actually used for anything, but it will be soon. * Currently this is not actually used for anything, but it will be soon.
@ -92,10 +92,10 @@ You can access the settings via the "Options" page of the main menu, or directly
If you'd like to build this yourself, you will need to have installed BepInEx and/or MelonLoader for at least one Unity game. If you want to build all 4 versions, you will need at least one IL2CPP and one Mono game, with BepInEx and MelonLoader installed for both. If you'd like to build this yourself, you will need to have installed BepInEx and/or MelonLoader for at least one Unity game. If you want to build all 4 versions, you will need at least one IL2CPP and one Mono game, with BepInEx and MelonLoader installed for both.
1. Install MelonLoader or BepInEx for your game. 1. Install BepInEx or MelonLoader for your game.
2. Open the `src\Explorer.csproj` file in a text editor. 2. Open the `src\UnityExplorer.csproj` file in a text editor.
3. Set the relevant `GameFolder` values for the versions you want to build, eg. set `MLCppGameFolder` if you want to build for a MelonLoader IL2CPP game. 3. For IL2CPP builds, make sure you set `BIECppGameFolder` (for BepInEx) and/or `MLCppGameFolder` (for MelonLoader) so the project can locate the necessary references.
4. Open the `src\Explorer.sln` project. 4. Open the `src\UnityExplorer.sln` project.
5. 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. 5. 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.
5. The DLLs are built to the `Release\` folder in the root of the repository. 5. The DLLs are built to the `Release\` folder in the root of the repository.
6. If ILRepack fails or is missing, use the NuGet package manager to re-install `ILRepack.Lib.MSBuild.Task`, then re-build. 6. If ILRepack fails or is missing, use the NuGet package manager to re-install `ILRepack.Lib.MSBuild.Task`, then re-build.

BIN
lib/INIFileParser.dll Normal file

Binary file not shown.

Binary file not shown.

View File

@ -1,18 +1,22 @@
using System; using System;
using System.IO; using System.IO;
using System.Xml.Serialization;
using UnityEngine; using UnityEngine;
using IniParser;
using IniParser.Parser;
namespace UnityExplorer.Config namespace UnityExplorer.Config
{ {
public class ModConfig public class ModConfig
{ {
[XmlIgnore] public static readonly XmlSerializer Serializer = new XmlSerializer(typeof(ModConfig)); public static ModConfig Instance;
//[XmlIgnore] private const string EXPLORER_FOLDER = @"Mods\UnityExplorer"; internal static readonly IniDataParser _parser = new IniDataParser();
[XmlIgnore] private const string SETTINGS_PATH = ExplorerCore.EXPLORER_FOLDER + @"\config.xml"; internal const string INI_PATH = ExplorerCore.EXPLORER_FOLDER + @"\config.ini";
[XmlIgnore] public static ModConfig Instance; static ModConfig()
{
_parser.Configuration.CommentString = "#";
}
// Actual configs // Actual configs
public KeyCode Main_Menu_Toggle = KeyCode.F7; public KeyCode Main_Menu_Toggle = KeyCode.F7;
@ -31,38 +35,66 @@ namespace UnityExplorer.Config
public static void OnLoad() public static void OnLoad()
{ {
Instance = new ModConfig();
if (LoadSettings()) if (LoadSettings())
return; return;
Instance = new ModConfig();
SaveSettings(); SaveSettings();
} }
public static bool LoadSettings() public static bool LoadSettings()
{ {
if (!File.Exists(SETTINGS_PATH)) if (!File.Exists(INI_PATH))
return false; return false;
try string ini = File.ReadAllText(INI_PATH);
var data = _parser.Parse(ini);
foreach (var config in data.Sections["Config"])
{ {
using (var file = File.OpenRead(SETTINGS_PATH)) switch (config.KeyName)
Instance = (ModConfig)Serializer.Deserialize(file); {
} case "Main_Menu_Toggle":
catch Instance.Main_Menu_Toggle = (KeyCode)Enum.Parse(typeof(KeyCode), config.Value);
{ break;
return false; case "Force_Unlock_Mouse":
Instance.Force_Unlock_Mouse = bool.Parse(config.Value);
break;
case "Default_Page_Limit":
Instance.Default_Page_Limit = int.Parse(config.Value);
break;
case "Log_Unity_Debug":
Instance.Log_Unity_Debug = bool.Parse(config.Value);
break;
case "Save_Logs_To_Disk":
Instance.Save_Logs_To_Disk = bool.Parse(config.Value);
break;
case "Default_Output_Path":
Instance.Default_Output_Path = config.Value;
break;
}
} }
return Instance != null; return true;
} }
public static void SaveSettings() public static void SaveSettings()
{ {
if (File.Exists(SETTINGS_PATH)) var data = new IniParser.Model.IniData();
File.Delete(SETTINGS_PATH);
using (var file = File.Create(SETTINGS_PATH)) data.Sections.AddSection("Config");
Serializer.Serialize(file, Instance);
var sec = data.Sections["Config"];
sec.AddKey("Main_Menu_Toggle", Instance.Main_Menu_Toggle.ToString());
sec.AddKey("Force_Unlock_Mouse", Instance.Force_Unlock_Mouse.ToString());
sec.AddKey("Default_Page_Limit", Instance.Default_Page_Limit.ToString());
sec.AddKey("Log_Unity_Debug", Instance.Log_Unity_Debug.ToString());
sec.AddKey("Save_Logs_To_Disk", Instance.Save_Logs_To_Disk.ToString());
sec.AddKey("Default_Output_Path", Instance.Default_Output_Path);
File.WriteAllText(INI_PATH, data.ToString());
} }
} }
} }

View File

@ -16,7 +16,7 @@ namespace UnityExplorer
public class ExplorerCore public class ExplorerCore
{ {
public const string NAME = "UnityExplorer"; public const string NAME = "UnityExplorer";
public const string VERSION = "3.0.4"; public const string VERSION = "3.0.6";
public const string AUTHOR = "Sinai"; public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer"; public const string GUID = "com.sinai.unityexplorer";
public const string EXPLORER_FOLDER = @"Mods\UnityExplorer"; public const string EXPLORER_FOLDER = @"Mods\UnityExplorer";
@ -90,6 +90,7 @@ namespace UnityExplorer
{ {
UIManager.Init(); UIManager.Init();
Log("Initialized UnityExplorer UI."); Log("Initialized UnityExplorer UI.");
// InspectorManager.Instance.Inspect(Tests.TestClass.Instance);
} }
catch (Exception e) catch (Exception e)
{ {

View File

@ -52,7 +52,7 @@ namespace UnityExplorer.Helpers
return list.ToArray(); return list.ToArray();
} }
public static Type GetActualType(object obj) public static Type GetActualType(this object obj)
{ {
if (obj == null) if (obj == null)
return null; return null;
@ -107,6 +107,19 @@ namespace UnityExplorer.Helpers
private static readonly Dictionary<Type, IntPtr> CppClassPointers = new Dictionary<Type, IntPtr>(); private static readonly Dictionary<Type, IntPtr> CppClassPointers = new Dictionary<Type, IntPtr>();
/// <summary>
/// Attempt to cast the object to its underlying type.
/// </summary>
/// <param name="obj">The object you want to cast.</param>
/// <returns>The object, as the underlying type if successful or the input value if not.</returns>
public static object Il2CppCast(this object obj) => Il2CppCast(obj, GetActualType(obj));
/// <summary>
/// Attempt to cast the object to the provided type.
/// </summary>
/// <param name="obj">The object you want to cast.</param>
/// <param name="castTo">The Type you want to cast to.</param>
/// <returns>The object, as the type (or a normal C# object) if successful or the input value if not.</returns>
public static object Il2CppCast(this object obj, Type castTo) public static object Il2CppCast(this object obj, Type castTo)
{ {
if (!(obj is Il2CppSystem.Object ilObj)) if (!(obj is Il2CppSystem.Object ilObj))
@ -126,6 +139,39 @@ namespace UnityExplorer.Helpers
return Activator.CreateInstance(castTo, ilObj.Pointer); return Activator.CreateInstance(castTo, ilObj.Pointer);
} }
internal static readonly Dictionary<Type, MethodInfo> s_unboxMethods = new Dictionary<Type, MethodInfo>();
/// <summary>
/// Attempt to unbox the object to the underlying struct type.
/// </summary>
/// <param name="obj">The object which is a struct underneath.</param>
/// <returns>The struct if successful, otherwise null.</returns>
public static object Unbox(this object obj) => Unbox(obj, GetActualType(obj));
/// <summary>
/// Attempt to unbox the object to the struct type.
/// </summary>
/// <param name="obj">The object which is a struct underneath.</param>
/// <param name="type">The type of the struct you want to unbox to.</param>
/// <returns>The struct if successful, otherwise null.</returns>
public static object Unbox(this object obj, Type type)
{
if (!type.IsValueType)
return null;
if (!(obj is Il2CppSystem.Object))
return obj;
if (!s_unboxMethods.ContainsKey(type))
{
s_unboxMethods.Add(type, typeof(Il2CppObjectBase)
.GetMethod("Unbox")
.MakeGenericMethod(type));
}
return s_unboxMethods[type].Invoke(obj, new object[0]);
}
public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _); public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _);
public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr) public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr)
@ -215,50 +261,56 @@ namespace UnityExplorer.Helpers
public static bool LoadModule(string module) => true; public static bool LoadModule(string module) => true;
#endif #endif
#if CPP
internal static IntPtr s_cppEnumerableClassPtr;
#endif
public static bool IsEnumerable(Type t) public static bool IsEnumerable(Type t)
{ {
if (typeof(IEnumerable).IsAssignableFrom(t)) if (typeof(IEnumerable).IsAssignableFrom(t))
{
return true; return true;
#if CPP
try
{
if (s_cppEnumerableClassPtr == IntPtr.Zero)
Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IEnumerable), out s_cppEnumerableClassPtr);
if (s_cppEnumerableClassPtr != IntPtr.Zero
&& Il2CppTypeNotNull(t, out IntPtr classPtr)
&& il2cpp_class_is_assignable_from(s_cppEnumerableClassPtr, classPtr))
{
return true;
}
} }
catch { }
#endif
return false;
}
#if CPP #if CPP
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g) internal static IntPtr s_cppDictionaryClassPtr;
{
return typeof(Il2CppSystem.Collections.Generic.List<>).IsAssignableFrom(g)
|| typeof(Il2CppSystem.Collections.Generic.IList<>).IsAssignableFrom(g)
|| typeof(Il2CppSystem.Collections.Generic.HashSet<>).IsAssignableFrom(g);
}
else
{
return typeof(Il2CppSystem.Collections.IList).IsAssignableFrom(t);
}
#else
return false;
#endif #endif
}
public static bool IsDictionary(Type t) public static bool IsDictionary(Type t)
{ {
if (typeof(IDictionary).IsAssignableFrom(t)) if (typeof(IDictionary).IsAssignableFrom(t))
{
return true; return true;
}
#if CPP #if CPP
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g) try
{ {
return typeof(Il2CppSystem.Collections.Generic.Dictionary<,>).IsAssignableFrom(g) if (s_cppDictionaryClassPtr == IntPtr.Zero)
|| typeof(Il2CppSystem.Collections.Generic.IDictionary<,>).IsAssignableFrom(g); if (!Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IDictionary), out s_cppDictionaryClassPtr))
return false;
if (Il2CppTypeNotNull(t, out IntPtr classPtr))
{
if (il2cpp_class_is_assignable_from(s_cppDictionaryClassPtr, classPtr))
return true;
}
} }
else catch { }
{
return typeof(Il2CppSystem.Collections.IDictionary).IsAssignableFrom(t)
|| typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(t);
}
#else
return false;
#endif #endif
return false;
} }
public static string ExceptionToString(Exception e, bool innerMost = false) public static string ExceptionToString(Exception e, bool innerMost = false)

View File

@ -10,8 +10,7 @@ namespace UnityExplorer.Helpers
{ {
public static class Texture2DHelpers public static class Texture2DHelpers
{ {
#if CPP // If Mono #if MONO
#else
private static bool isNewEncodeMethod = false; private static bool isNewEncodeMethod = false;
private static MethodInfo EncodeToPNGMethod => m_encodeToPNGMethod ?? GetEncodeToPNGMethod(); private static MethodInfo EncodeToPNGMethod => m_encodeToPNGMethod ?? GetEncodeToPNGMethod();
private static MethodInfo m_encodeToPNGMethod; private static MethodInfo m_encodeToPNGMethod;
@ -52,7 +51,7 @@ namespace UnityExplorer.Helpers
} }
} }
public static Texture2D Copy(Texture2D orig, Rect rect) //, bool isDTXnmNormal = false) public static Texture2D Copy(Texture2D orig, Rect rect)
{ {
Color[] pixels; Color[] pixels;

View File

@ -5,6 +5,7 @@
<ItemGroup> <ItemGroup>
<InputAssemblies Include="$(OutputPath)$(AssemblyName).dll" /> <InputAssemblies Include="$(OutputPath)$(AssemblyName).dll" />
<InputAssemblies Include="..\lib\mcs.dll" /> <InputAssemblies Include="..\lib\mcs.dll" />
<InputAssemblies Include="..\lib\INIFileParser.dll" />
</ItemGroup> </ItemGroup>
<ILRepack <ILRepack

View File

@ -80,7 +80,11 @@ namespace UnityExplorer.Inspectors.GameObjects
text.text = UISyntaxHighlight.ParseFullSyntax(ReflectionHelpers.GetActualType(comp), true); text.text = UISyntaxHighlight.ParseFullSyntax(ReflectionHelpers.GetActualType(comp), true);
var toggle = s_compToggles[i]; var toggle = s_compToggles[i];
#if CPP
if (comp.TryCast<Behaviour>() is Behaviour behaviour)
#else
if (comp is Behaviour behaviour) if (comp is Behaviour behaviour)
#endif
{ {
if (!toggle.gameObject.activeSelf) if (!toggle.gameObject.activeSelf)
toggle.gameObject.SetActive(true); toggle.gameObject.SetActive(true);
@ -130,7 +134,7 @@ namespace UnityExplorer.Inspectors.GameObjects
} }
#region UI CONSTRUCTION #region UI CONSTRUCTION
internal void ConstructCompList(GameObject parent) internal void ConstructCompList(GameObject parent)
{ {
@ -168,34 +172,34 @@ namespace UnityExplorer.Inspectors.GameObjects
{ {
int thisIndex = s_compListTexts.Count; int thisIndex = s_compListTexts.Count;
GameObject btnGroupObj = UIFactory.CreateHorizontalGroup(s_compListContent, new Color(0.07f, 0.07f, 0.07f)); GameObject groupObj = UIFactory.CreateHorizontalGroup(s_compListContent, new Color(0.07f, 0.07f, 0.07f));
HorizontalLayoutGroup btnGroup = btnGroupObj.GetComponent<HorizontalLayoutGroup>(); HorizontalLayoutGroup group = groupObj.GetComponent<HorizontalLayoutGroup>();
btnGroup.childForceExpandWidth = true; group.childForceExpandWidth = true;
btnGroup.childControlWidth = true; group.childControlWidth = true;
btnGroup.childForceExpandHeight = false; group.childForceExpandHeight = false;
btnGroup.childControlHeight = true; group.childControlHeight = true;
btnGroup.childAlignment = TextAnchor.MiddleLeft; group.childAlignment = TextAnchor.MiddleLeft;
LayoutElement btnLayout = btnGroupObj.AddComponent<LayoutElement>(); LayoutElement groupLayout = groupObj.AddComponent<LayoutElement>();
btnLayout.minWidth = 25; groupLayout.minWidth = 25;
btnLayout.flexibleWidth = 999; groupLayout.flexibleWidth = 999;
btnLayout.minHeight = 25; groupLayout.minHeight = 25;
btnLayout.flexibleHeight = 0; groupLayout.flexibleHeight = 0;
btnGroupObj.AddComponent<Mask>(); groupObj.AddComponent<Mask>();
// Behaviour enabled toggle // Behaviour enabled toggle
var toggleObj = UIFactory.CreateToggle(btnGroupObj, out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f)); var toggleObj = UIFactory.CreateToggle(groupObj, out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f));
var toggleLayout = toggleObj.AddComponent<LayoutElement>(); var toggleLayout = toggleObj.AddComponent<LayoutElement>();
toggleLayout.minHeight = 25; toggleLayout.minHeight = 25;
toggleLayout.minWidth = 25; toggleLayout.minWidth = 25;
toggleText.text = ""; toggleText.text = "";
toggle.isOn = false; toggle.isOn = true;
s_compToggles.Add(toggle); s_compToggles.Add(toggle);
toggle.onValueChanged.AddListener((bool val) => { OnCompToggleClicked(thisIndex, val); }); toggle.onValueChanged.AddListener((bool val) => { OnCompToggleClicked(thisIndex, val); });
// Main component button // Main component button
GameObject mainButtonObj = UIFactory.CreateButton(btnGroupObj); GameObject mainButtonObj = UIFactory.CreateButton(groupObj);
LayoutElement mainBtnLayout = mainButtonObj.AddComponent<LayoutElement>(); LayoutElement mainBtnLayout = mainButtonObj.AddComponent<LayoutElement>();
mainBtnLayout.minHeight = 25; mainBtnLayout.minHeight = 25;
mainBtnLayout.flexibleHeight = 0; mainBtnLayout.flexibleHeight = 0;
@ -224,6 +228,6 @@ namespace UnityExplorer.Inspectors.GameObjects
} }
#endregion #endregion
} }
} }

View File

@ -71,6 +71,9 @@ namespace UnityExplorer.Inspectors.Reflection
} }
catch (Exception e) catch (Exception e)
{ {
while (e.InnerException != null)
e = e.InnerException;
ExplorerCore.LogWarning($"Exception evaluating: {e.GetType()}, {e.Message}"); ExplorerCore.LogWarning($"Exception evaluating: {e.GetType()}, {e.Message}");
ReflectionException = ReflectionHelpers.ExceptionToString(e); ReflectionException = ReflectionHelpers.ExceptionToString(e);
} }

View File

@ -10,6 +10,9 @@ using UnityExplorer.Helpers;
using UnityExplorer.UI; using UnityExplorer.UI;
using UnityExplorer.UI.Shared; using UnityExplorer.UI.Shared;
using System.Reflection; using System.Reflection;
#if CPP
using CppDictionary = Il2CppSystem.Collections.IDictionary;
#endif
namespace UnityExplorer.Inspectors.Reflection namespace UnityExplorer.Inspectors.Reflection
{ {
@ -44,7 +47,11 @@ namespace UnityExplorer.Inspectors.Reflection
} }
internal IDictionary RefIDictionary; internal IDictionary RefIDictionary;
#if CPP
internal CppDictionary RefCppDictionary;
#else
internal IDictionary RefCppDictionary = null;
#endif
internal Type m_typeOfKeys; internal Type m_typeOfKeys;
internal Type m_typeofValues; internal Type m_typeofValues;
@ -65,6 +72,11 @@ namespace UnityExplorer.Inspectors.Reflection
{ {
RefIDictionary = Value as IDictionary; RefIDictionary = Value as IDictionary;
#if CPP
try { RefCppDictionary = (Value as Il2CppSystem.Object).TryCast<CppDictionary>(); }
catch { }
#endif
if (m_subContentParent.activeSelf) if (m_subContentParent.activeSelf)
{ {
GetCacheEntries(); GetCacheEntries();
@ -129,13 +141,6 @@ namespace UnityExplorer.Inspectors.Reflection
{ {
var value = RefIDictionary[key]; var value = RefIDictionary[key];
//if (index >= m_rowHolders.Count)
//{
// AddRowHolder();
//}
//var holder = m_rowHolders[index];
var cacheKey = new CachePaired(index, this, this.RefIDictionary, PairTypes.Key, m_listContent); var cacheKey = new CachePaired(index, this, this.RefIDictionary, PairTypes.Key, m_listContent);
cacheKey.CreateIValue(key, this.m_typeOfKeys); cacheKey.CreateIValue(key, this.m_typeOfKeys);
cacheKey.Disable(); cacheKey.Disable();
@ -206,9 +211,10 @@ namespace UnityExplorer.Inspectors.Reflection
RefreshDisplay(); RefreshDisplay();
} }
#region CPP fixes #region CPP fixes
#if CPP #if CPP
// temp fix for Il2Cpp IDictionary until interfaces are fixed // temp fix for Il2Cpp IDictionary until interfaces are fixed
private IDictionary EnumerateWithReflection() private IDictionary EnumerateWithReflection()
{ {
var valueType = Value?.GetType() ?? FallbackType; var valueType = Value?.GetType() ?? FallbackType;
@ -222,8 +228,8 @@ namespace UnityExplorer.Inspectors.Reflection
var valueList = new List<object>(); var valueList = new List<object>();
// store entries with reflection // store entries with reflection
EnumerateWithReflection(keys, keyList); EnumerateCollection(keys, keyList);
EnumerateWithReflection(values, valueList); EnumerateCollection(values, valueList);
// make actual mono dictionary // make actual mono dictionary
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>) var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
@ -236,7 +242,7 @@ namespace UnityExplorer.Inspectors.Reflection
return dict; return dict;
} }
private void EnumerateWithReflection(object collection, List<object> list) private void EnumerateCollection(object collection, List<object> list)
{ {
// invoke GetEnumerator // invoke GetEnumerator
var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null); var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null);
@ -253,9 +259,9 @@ namespace UnityExplorer.Inspectors.Reflection
} }
#endif #endif
#endregion #endregion
#region UI CONSTRUCTION #region UI CONSTRUCTION
internal GameObject m_listContent; internal GameObject m_listContent;
internal LayoutElement m_listLayout; internal LayoutElement m_listLayout;
@ -309,6 +315,6 @@ namespace UnityExplorer.Inspectors.Reflection
// m_rowHolders.Add(obj); // m_rowHolders.Add(obj);
//} //}
#endregion #endregion
} }
} }

View File

@ -37,6 +37,11 @@ namespace UnityExplorer.Inspectors.Reflection
internal IEnumerable RefIEnumerable; internal IEnumerable RefIEnumerable;
internal IList RefIList; internal IList RefIList;
#if CPP
internal Il2CppSystem.Collections.ICollection CppICollection;
#else
internal ICollection CppICollection = null;
#endif
internal readonly Type m_baseEntryType; internal readonly Type m_baseEntryType;
@ -49,6 +54,14 @@ namespace UnityExplorer.Inspectors.Reflection
RefIEnumerable = Value as IEnumerable; RefIEnumerable = Value as IEnumerable;
RefIList = Value as IList; RefIList = Value as IList;
#if CPP
if (Value != null && RefIList == null)
{
try { CppICollection = (Value as Il2CppSystem.Object).TryCast<Il2CppSystem.Collections.ICollection>(); }
catch { }
}
#endif
if (m_subContentParent.activeSelf) if (m_subContentParent.activeSelf)
{ {
GetCacheEntries(); GetCacheEntries();
@ -77,8 +90,8 @@ namespace UnityExplorer.Inspectors.Reflection
if (Value != null) if (Value != null)
{ {
string count = "?"; string count = "?";
if (m_recacheWanted && RefIList != null) if (m_recacheWanted && (RefIList != null || CppICollection != null))
count = RefIList.Count.ToString(); count = RefIList?.Count.ToString() ?? CppICollection.Count.ToString();
else if (!m_recacheWanted) else if (!m_recacheWanted)
count = m_entries.Count.ToString(); count = m_entries.Count.ToString();
@ -169,89 +182,62 @@ namespace UnityExplorer.Inspectors.Reflection
RefreshDisplay(); RefreshDisplay();
} }
#region CPP Helpers #region CPP Helpers
#if CPP #if CPP
// some temp fixes for Il2Cpp IEnumerables until interfaces are fixed // some temp fixes for Il2Cpp IEnumerables until interfaces are fixed
internal static readonly Dictionary<Type, MethodInfo> s_getEnumeratorMethods = new Dictionary<Type, MethodInfo>();
internal static readonly Dictionary<Type, EnumeratorInfo> s_enumeratorInfos = new Dictionary<Type, EnumeratorInfo>();
internal class EnumeratorInfo
{
internal MethodInfo moveNext;
internal PropertyInfo current;
}
private IEnumerable EnumerateWithReflection() private IEnumerable EnumerateWithReflection()
{ {
if (Value.IsNullOrDestroyed()) if (Value == null)
return null; return null;
var genericDef = Value.GetType().GetGenericTypeDefinition(); // new test
var CppEnumerable = (Value as Il2CppSystem.Object)?.TryCast<Il2CppSystem.Collections.IEnumerable>();
if (genericDef == typeof(Il2CppSystem.Collections.Generic.List<>)) if (CppEnumerable != null)
return CppListToMono(genericDef);
else if (genericDef == typeof(Il2CppSystem.Collections.Generic.HashSet<>))
return CppHashSetToMono();
else
return CppIListToMono();
}
// List<T>.ToArray()
private IEnumerable CppListToMono(Type genericTypeDef)
{
if (genericTypeDef == null) return null;
return genericTypeDef
.MakeGenericType(new Type[] { this.m_baseEntryType })
.GetMethod("ToArray")
.Invoke(Value, new object[0]) as IEnumerable;
}
// HashSet.GetEnumerator
private IEnumerable CppHashSetToMono()
{
var set = new HashSet<object>();
// invoke GetEnumerator
var enumerator = Value.GetType().GetMethod("GetEnumerator").Invoke(Value, null);
// get the type of it
var enumeratorType = enumerator.GetType();
// reflect MoveNext and Current
var moveNext = enumeratorType.GetMethod("MoveNext");
var current = enumeratorType.GetProperty("Current");
// iterate
while ((bool)moveNext.Invoke(enumerator, null))
set.Add(current.GetValue(enumerator));
return set;
}
// IList.Item
private IList CppIListToMono()
{
try
{ {
var genericType = typeof(List<>).MakeGenericType(new Type[] { this.m_baseEntryType }); var type = Value.GetType();
var list = (IList)Activator.CreateInstance(genericType); if (!s_getEnumeratorMethods.ContainsKey(type))
s_getEnumeratorMethods.Add(type, type.GetMethod("GetEnumerator"));
for (int i = 0; ; i++) var enumerator = s_getEnumeratorMethods[type].Invoke(Value, null);
var enumeratorType = enumerator.GetType();
if (!s_enumeratorInfos.ContainsKey(enumeratorType))
{ {
try s_enumeratorInfos.Add(enumeratorType, new EnumeratorInfo
{ {
var itm = Value?.GetType() current = enumeratorType.GetProperty("Current"),
.GetProperty("Item") moveNext = enumeratorType.GetMethod("MoveNext"),
.GetValue(Value, new object[] { i }); });
list.Add(itm);
}
catch { break; }
} }
var info = s_enumeratorInfos[enumeratorType];
// iterate
var list = new List<object>();
while ((bool)info.moveNext.Invoke(enumerator, null))
list.Add(info.current.GetValue(enumerator));
return list; return list;
} }
catch (Exception e)
{ return null;
ExplorerCore.Log("Exception converting Il2Cpp IList to Mono IList: " + e.GetType() + ", " + e.Message);
return null;
}
} }
#endif #endif
#endregion #endregion
#region UI CONSTRUCTION #region UI CONSTRUCTION
internal GameObject m_listContent; internal GameObject m_listContent;
internal LayoutElement m_listLayout; internal LayoutElement m_listLayout;
@ -296,6 +282,6 @@ namespace UnityExplorer.Inspectors.Reflection
contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize; contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
} }
#endregion #endregion
} }
} }

View File

@ -3,7 +3,11 @@ using System.Collections.Generic;
using UnityExplorer.UI; using UnityExplorer.UI;
using UnityEngine; using UnityEngine;
using System; using System;
using System.Runtime.InteropServices;
using System.Text;
#if CPP #if CPP
using UnhollowerBaseLib;
using UnityExplorer.Helpers;
#endif #endif
namespace UnityExplorer.Tests namespace UnityExplorer.Tests
@ -19,7 +23,6 @@ namespace UnityExplorer.Tests
"three", "three",
}; };
public static void StaticMethod() { } public static void StaticMethod() { }
} }
public class TestClass public class TestClass
@ -125,6 +128,8 @@ namespace UnityExplorer.Tests
public static Il2CppSystem.Collections.Generic.HashSet<string> CppHashSetTest; public static Il2CppSystem.Collections.Generic.HashSet<string> CppHashSetTest;
public static Il2CppSystem.Collections.Generic.List<string> CppStringTest; public static Il2CppSystem.Collections.Generic.List<string> CppStringTest;
public static Il2CppSystem.Collections.IList CppIList; public static Il2CppSystem.Collections.IList CppIList;
public static Il2CppSystem.Collections.Generic.Dictionary<string, string> CppDictTest;
public static Il2CppSystem.Collections.Generic.Dictionary<int, float> CppDictTest2;
#endif #endif
public TestClass() public TestClass()
@ -140,20 +145,7 @@ namespace UnityExplorer.Tests
} }
#if CPP #if CPP
TestTexture = UIManager.MakeSolidTexture(Color.white, 1000, 600); TextureSpriteTest();
TestTexture.name = "TestTexture";
var r = new Rect(0, 0, TestTexture.width, TestTexture.height);
var v2 = Vector2.zero;
var v4 = Vector4.zero;
TestSprite = Sprite.CreateSprite_Injected((Texture2D)TestTexture, ref r, ref v2, 100f, 0u, SpriteMeshType.Tight, ref v4, false);
GameObject.DontDestroyOnLoad(TestTexture);
GameObject.DontDestroyOnLoad(TestSprite);
//// test loading a tex from file
//var dataToLoad = System.IO.File.ReadAllBytes(@"Mods\UnityExplorer\Tex_Nemundis_Nebula.png");
//ExplorerCore.Log($"Tex load success: {TestTexture.LoadImage(dataToLoad, false)}");
CppHashSetTest = new Il2CppSystem.Collections.Generic.HashSet<string>(); CppHashSetTest = new Il2CppSystem.Collections.Generic.HashSet<string>();
CppHashSetTest.Add("1"); CppHashSetTest.Add("1");
@ -163,9 +155,38 @@ namespace UnityExplorer.Tests
CppStringTest = new Il2CppSystem.Collections.Generic.List<string>(); CppStringTest = new Il2CppSystem.Collections.Generic.List<string>();
CppStringTest.Add("1"); CppStringTest.Add("1");
CppStringTest.Add("2"); CppStringTest.Add("2");
CppDictTest = new Il2CppSystem.Collections.Generic.Dictionary<string, string>();
CppDictTest.Add("key1", "value1");
CppDictTest.Add("key2", "value2");
CppDictTest.Add("key3", "value3");
CppDictTest2 = new Il2CppSystem.Collections.Generic.Dictionary<int, float>();
CppDictTest2.Add(0, 0.5f);
CppDictTest2.Add(1, 0.5f);
CppDictTest2.Add(2, 0.5f);
#endif #endif
} }
private void TextureSpriteTest()
{
//TestTexture = UIManager.MakeSolidTexture(Color.white, 1000, 600);
//TestTexture = new Texture();
//TestTexture.name = "TestTexture";
//var r = new Rect(0, 0, TestTexture.width, TestTexture.height);
//var v2 = Vector2.zero;
//var v4 = Vector4.zero;
//TestSprite = Sprite.CreateSprite_Injected((Texture2D)TestTexture, ref r, ref v2, 100f, 0u, SpriteMeshType.Tight, ref v4, false);
//GameObject.DontDestroyOnLoad(TestTexture);
//GameObject.DontDestroyOnLoad(TestSprite);
//// test loading a tex from file
//var dataToLoad = System.IO.File.ReadAllBytes(@"Mods\UnityExplorer\Tex_Nemundis_Nebula.png");
//ExplorerCore.Log($"Tex load success: {TestTexture.LoadImage(dataToLoad, false)}");
}
public static string TestRefInOutGeneric<T>(ref string arg0, in int arg1, out string arg2) where T : Component public static string TestRefInOutGeneric<T>(ref string arg0, in int arg1, out string arg2) where T : Component
{ {
arg2 = "this is arg2"; arg2 = "this is arg2";

View File

@ -4,7 +4,6 @@ using UnityEngine.UI;
using UnityExplorer.Inspectors; using UnityExplorer.Inspectors;
using UnityExplorer.UI.Modules; using UnityExplorer.UI.Modules;
using System.IO; using System.IO;
//using TMPro;
using System.Reflection; using System.Reflection;
using UnityExplorer.Helpers; using UnityExplorer.Helpers;
using UnityExplorer.UI.Shared; using UnityExplorer.UI.Shared;
@ -20,9 +19,11 @@ namespace UnityExplorer.UI
public static GameObject CanvasRoot { get; private set; } public static GameObject CanvasRoot { get; private set; }
public static EventSystem EventSys { get; private set; } public static EventSystem EventSys { get; private set; }
internal static Sprite ResizeCursor { get; private set; }
internal static Font ConsoleFont { get; private set; } internal static Font ConsoleFont { get; private set; }
internal static Sprite ResizeCursor { get; private set; }
internal static Shader BackupShader { get; private set; }
public static void Init() public static void Init()
{ {
LoadBundle(); LoadBundle();
@ -100,11 +101,13 @@ namespace UnityExplorer.UI
{ {
var bundle = AssetBundle.LoadFromFile(bundlePath); var bundle = AssetBundle.LoadFromFile(bundlePath);
BackupShader = bundle.LoadAsset<Shader>("DefaultUI");
// Fix for games which don't ship with 'UI/Default' shader. // Fix for games which don't ship with 'UI/Default' shader.
if (Graphic.defaultGraphicMaterial.shader?.name != "UI/Default") if (Graphic.defaultGraphicMaterial.shader?.name != "UI/Default")
{ {
ExplorerCore.Log("This game does not ship with the 'UI/Default' shader, using manual Default Shader..."); ExplorerCore.Log("This game does not ship with the 'UI/Default' shader, using manual Default Shader...");
Graphic.defaultGraphicMaterial.shader = bundle.LoadAsset<Shader>("DefaultUI"); Graphic.defaultGraphicMaterial.shader = BackupShader;
} }
ResizeCursor = bundle.LoadAsset<Sprite>("cursor"); ResizeCursor = bundle.LoadAsset<Sprite>("cursor");
@ -146,37 +149,5 @@ namespace UnityExplorer.UI
return rootObj; return rootObj;
} }
public static Sprite CreateSprite(Texture2D tex, Rect size = default)
{
#if CPP
Vector2 pivot = Vector2.zero;
Vector4 border = Vector4.zero;
if (size == default)
{
size = new Rect(0, 0, tex.width, tex.height);
}
return Sprite.CreateSprite_Injected(tex, ref size, ref pivot, 100f, 0u, SpriteMeshType.Tight, ref border, false);
#else
return Sprite.Create(tex, size, Vector2.zero);
#endif
}
public static Texture2D MakeSolidTexture(Color color, int width, int height)
{
Color[] pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++)
{
pixels[i] = color;
}
Texture2D tex = new Texture2D(width, height);
tex.SetPixels(pixels);
tex.Apply();
return tex;
}
} }
} }

View File

@ -26,16 +26,8 @@
<RootNamespace>UnityExplorer</RootNamespace> <RootNamespace>UnityExplorer</RootNamespace>
<!-- Set this to the BepInEx Il2Cpp Game folder, without the ending '\' character. --> <!-- Set this to the BepInEx Il2Cpp Game folder, without the ending '\' character. -->
<BIECppGameFolder>D:\source\Unity Projects\Test\_BUILD</BIECppGameFolder> <BIECppGameFolder>D:\source\Unity Projects\Test\_BUILD</BIECppGameFolder>
<!-- Set this to the BepInEx Mono Game folder, without the ending '\' character. -->
<BIEMonoGameFolder>D:\source\Unity Projects\Test\_BUILD_MONO</BIEMonoGameFolder>
<!-- Set this to the BepInEx Mono Managed folder, without the ending '\' character. -->
<BIEMonoManagedFolder>D:\source\Unity Projects\Test\_BUILD_MONO\Test_Data\Managed</BIEMonoManagedFolder>
<!-- Set this to the MelonLoader Il2Cpp Game folder, without the ending '\' character. --> <!-- Set this to the MelonLoader Il2Cpp Game folder, without the ending '\' character. -->
<MLCppGameFolder>D:\source\Unity Projects\Test\_BUILD</MLCppGameFolder> <MLCppGameFolder>D:\source\Unity Projects\Test\_BUILD</MLCppGameFolder>
<!-- Set this to the MelonLoader Mono Game folder, without the ending '\' character. -->
<MLMonoGameFolder>D:\source\Unity Projects\Test\_BUILD_MONO</MLMonoGameFolder>
<!-- Set this to the MelonLoader Mono Managed folder, without the ending '\' character. -->
<MLMonoManagedFolder>D:\source\Unity Projects\Test\_BUILD_MONO\Test_Data\Managed</MLMonoManagedFolder>
<NuGetPackageImportStamp> <NuGetPackageImportStamp>
</NuGetPackageImportStamp> </NuGetPackageImportStamp>
</PropertyGroup> </PropertyGroup>
@ -77,6 +69,10 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="INIFileParser, Version=2.5.2.0, Culture=neutral, PublicKeyToken=79af7b307b65cf3c, processorArchitecture=MSIL">
<HintPath>packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Core" /> <Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" /> <Reference Include="System.Xml.Linq" />
@ -103,7 +99,7 @@
<!-- MelonLoader Mono refs --> <!-- MelonLoader Mono refs -->
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='true|false'"> <ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='true|false'">
<Reference Include="MelonLoader.ModHandler"> <Reference Include="MelonLoader.ModHandler">
<HintPath>$(MLMonoGameFolder)\MelonLoader\MelonLoader.ModHandler.dll</HintPath> <HintPath>..\lib\MelonLoader.ModHandler.dll</HintPath>
<Private>False</Private> <Private>False</Private>
</Reference> </Reference>
</ItemGroup> </ItemGroup>

View File

@ -10,6 +10,16 @@ namespace UnityExplorer.Unstrip
{ {
public static class ImageConversionUnstrip public static class ImageConversionUnstrip
{ {
// LoadImage helper from a filepath
public static bool LoadImage(Texture2D tex, string filePath, bool markNonReadable)
{
if (!File.Exists(filePath))
return false;
return tex.LoadImage(File.ReadAllBytes(filePath), markNonReadable);
}
#if CPP #if CPP
// byte[] ImageConversion.EncodeToPNG(this Texture2D image); // byte[] ImageConversion.EncodeToPNG(this Texture2D image);
@ -17,8 +27,12 @@ namespace UnityExplorer.Unstrip
public static byte[] EncodeToPNG(this Texture2D tex) public static byte[] EncodeToPNG(this Texture2D tex)
{ {
IntPtr ptr = ICallHelper.GetICall<d_EncodeToPNG>("UnityEngine.ImageConversion::EncodeToPNG") var iCall = ICallHelper.GetICall<d_EncodeToPNG>("UnityEngine.ImageConversion::EncodeToPNG");
.Invoke(tex.Pointer);
IntPtr ptr = iCall.Invoke(tex.Pointer);
if (ptr == IntPtr.Zero)
return null;
return new Il2CppStructArray<byte>(ptr); return new Il2CppStructArray<byte>(ptr);
} }
@ -38,19 +52,23 @@ namespace UnityExplorer.Unstrip
return ret; return ret;
} }
#endif
// Helper for LoadImage from filepath // Sprite Sprite.Create
public static bool LoadImage(Texture2D tex, string filePath, bool markNonReadable) internal delegate IntPtr d_CreateSprite(IntPtr texture, ref Rect rect, ref Vector2 pivot, float pixelsPerUnit,
uint extrude, int meshType, ref Vector4 border, bool generateFallbackPhysicsShape);
public static Sprite CreateSprite(Texture texture, Rect rect, Vector2 pivot, float pixelsPerUnit, uint extrude, Vector4 border)
{ {
if (!File.Exists(filePath)) var iCall = ICallHelper.GetICall<d_CreateSprite>("UnityEngine.Sprite::CreateSprite_Injected");
{
return false;
}
byte[] data = File.ReadAllBytes(filePath); var ptr = iCall.Invoke(texture.Pointer, ref rect, ref pivot, pixelsPerUnit, extrude, 1, ref border, false);
return tex.LoadImage(data, markNonReadable);
if (ptr == IntPtr.Zero)
return null;
else
return new Sprite(ptr);
} }
#endif
} }
} }

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="ILRepack.Lib.MSBuild.Task" version="2.0.18.1" targetFramework="net472" /> <package id="ILRepack.Lib.MSBuild.Task" version="2.0.18.1" targetFramework="net472" />
<package id="ini-parser" version="2.5.2" targetFramework="net35" />
</packages> </packages>