Compare commits

...

57 Commits
4.1.8 ... 4.2.1

Author SHA1 Message Date
fae85c2968 Bump version 2021-08-23 18:35:36 +10:00
fc8fa9aa7a Fix TestClass init mistake 2021-08-23 18:35:31 +10:00
bcf9a801a9 Fix suggestions still being given in comments sometimes 2021-08-23 18:35:22 +10:00
20298aa47b Make Type and Member names selectable / copy+paste-able 2021-08-23 18:35:08 +10:00
e2c1c186c3 Add wrapper to handle LemonAction issue 2021-08-23 18:31:26 +10:00
9ce6508828 Update README.md 2021-08-19 16:20:41 +10:00
506e75c5fe Handle Unhollowed modules path through IExplorerLoader. Allow Standalone release to specify manually. 2021-08-19 16:20:25 +10:00
a2f22051f0 Cleanup 2021-08-19 16:19:49 +10:00
4e3203a91b Make Assembly.GetTypes patch a finalizer instead of prefix 2021-08-10 17:31:12 +10:00
a411ce2dba Add patch for Assembly.GetTypes to catch exceptions 2021-08-10 17:17:44 +10:00
48132b3d46 Cleanup ILRepack 2021-08-10 17:17:21 +10:00
e49ed3028f Cleanup 2021-08-06 18:44:13 +10:00
31dd54d25c Reduce string alloc 2021-08-06 16:02:58 +10:00
3cfdd6fa43 Update readme -noci 2021-08-04 01:16:52 +10:00
1fa1283a68 Cleanup and update readme 2021-08-03 18:30:07 +10:00
afa4135b67 Update ML lib for "LemonCleanup" breaking changes 2021-08-03 16:12:28 +10:00
48d1cf574d Fix evaluate exception logic with methods that have arguments 2021-08-03 16:11:54 +10:00
df0abbc847 Small optimizations/cleanup 2021-08-03 16:11:16 +10:00
5c9dcb1d43 Bump HarmonyX to 2.5.2 2021-07-28 19:14:37 +10:00
602770d980 Cleanup 2021-07-28 19:01:43 +10:00
f26371f95f Go back to previous panel transform saving, no need to change 2021-07-28 18:27:10 +10:00
a673c39f4a Include arial in legacy bundles 2021-07-28 18:26:53 +10:00
40583cae3d Include arial in legacy bundles, cleanup 2021-07-28 06:41:02 +10:00
20018a9ba9 Use invariant decimal separator for NumberFormatString 2021-07-28 06:40:05 +10:00
dabf92a1a5 Use CurrentCulture, and use whitespace instead of comma as separator 2021-07-27 22:17:26 +10:00
b7e275f02c Cleanup 2021-07-27 22:12:48 +10:00
f393e0d706 Include Arial font, don't rely on built-in resource
TODO add font to legacy bundles
2021-07-27 18:14:44 +10:00
ab8acc9e84 Change directory case 2021-07-21 20:53:03 +10:00
66c30ee70e Cleanup 2021-07-21 20:48:21 +10:00
ddd271c00d Cleanup 2021-07-21 20:12:34 +10:00
30fe9e4dde Cleanup 2021-07-21 18:37:01 +10:00
fa3a436037 Cleanup 2021-07-21 18:29:01 +10:00
ca27d2b20f Update CursorUnlocker.cs 2021-07-21 05:18:22 +10:00
4315e0c547 Merge branch 'master' of https://github.com/sinai-dev/UnityExplorer 2021-07-21 04:23:55 +10:00
3e19e74329 Update readme -noci 2021-07-21 04:17:41 +10:00
5a3ffebadc Remove legacy build 2021-07-21 04:16:53 +10:00
6e6b6239d8 Do patching in CursorUnlocker, add patch for SetSelectedGameObject 2021-07-21 04:14:34 +10:00
03c8e5a8bd Add Harmony instance to ExplorerCore, bump version 2021-07-21 04:14:12 +10:00
44f7209843 Remove ML 0.3 builds 2021-07-21 04:13:54 +10:00
734e45cf9f Remove patching in mod loader classes 2021-07-21 04:13:42 +10:00
97838e0b3a Update Unhollower 2021-07-21 04:13:15 +10:00
304b47f898 Bump version 2021-07-19 21:43:40 +10:00
3d1fcbcd9f Fix duplicate Scene hashcodes breaking PopulateSceneDropdown 2021-07-19 21:43:36 +10:00
36fc17aa43 Get rid of pointless ReadOnlyCollection properties 2021-07-19 21:43:16 +10:00
adfa29e63c Bump version 2021-07-15 05:20:31 +10:00
3ffdcea73b Force all devices to always be supported by InputSystem 2021-07-15 05:20:26 +10:00
dfd55260a8 Fallback to null check loading if unityVersion throws an exception 2021-07-15 05:19:48 +10:00
29c78dc5a6 Manually get Major and Minor instead of using Version class 2021-07-11 23:22:33 +10:00
bf59d9d6cd Handle cases where Unity doesn't include 'f' suffix on version
Not sure if this ever happens but just in case
2021-07-11 18:19:09 +10:00
bb0c59534a Bump version 2021-07-11 18:11:18 +10:00
802bb722bc Use Application.unityVersion to load appropriate AssetBundle
And fix issue with Dropdowns and Time input label not refreshing properly on launch
2021-07-11 18:11:14 +10:00
9ca992b0d7 Fix incorrect config description example 2021-07-07 23:27:43 +10:00
5f3b3a6870 cleanup -noci 2021-07-07 23:21:40 +10:00
f5bce439cb Favor public GetEnumerator over private. Use GetActualType. Improve logging. 2021-07-06 16:42:20 +10:00
e2b2c9038a Add 'other' issue template -noci 2021-07-05 19:05:04 +10:00
fbefccd6b7 Update issue template -noci 2021-07-02 23:32:02 +10:00
271c91f0d0 Update README.md 2021-07-02 20:03:41 +10:00
61 changed files with 799 additions and 1034 deletions

View File

@ -24,10 +24,8 @@ body:
- BepInEx IL2CPP
- BepInEx 6.X Mono
- BepInEx 5.X Mono
- MelonLoader 0.4+ IL2CPP
- MelonLoader 0.4+ Mono
- MelonLoader 0.3 IL2CPP
- MelonLoader 0.3 Mono
- MelonLoader IL2CPP
- MelonLoader Mono
- Standalone IL2CPP
- Standalone Mono
validations:
@ -50,12 +48,13 @@ body:
- type: textarea
id: logs
attributes:
label: Log output
label: Relevant log output
description: |
Please copy and paste your mod loader's log output.
Please copy and paste any relevant logs and stack traces.
* Unity log: `%userprofile%\AppData\LocalLow\{Company}\{Game}\Player.log` or `output_log.txt`
* BepInEx: `BepInEx\LogOutput.log`
* MelonLoader: `MelonLoader\latest.log`
* Standalone: `{DLL_Location}\UnityExplorer\Logs\` (pick the most recent one)
render: shell
validations:
required: true
required: false

13
.github/ISSUE_TEMPLATE/other.yaml vendored Normal file
View File

@ -0,0 +1,13 @@
name: Other
description: Something else?
title: "[Other]: "
labels: [Other]
body:
- type: textarea
id: description
attributes:
label: Describe the issue
description: |
Please describe the issue in as much detail as possible.
validations:
required: true

View File

@ -79,22 +79,6 @@ jobs:
name: UnityExplorer.MelonLoader.Mono
path: ./Release/UnityExplorer.MelonLoader.Mono/*
# MelonLoader 0.3.0 Il2Cpp
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_MLLegacy_Cpp
- uses: actions/upload-artifact@v2
with:
name: UnityExplorer.MelonLoader_Legacy.Il2Cpp
path: ./Release/UnityExplorer.MelonLoader_Legacy.Il2Cpp/*
# MelonLoader 0.3.0 Mono
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_MLLegacy_Mono
- uses: actions/upload-artifact@v2
with:
name: UnityExplorer.MelonLoader_Legacy.Mono
path: ./Release/UnityExplorer.MelonLoader_Legacy.Mono/*
# Standalone Il2Cpp
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_STANDALONE_Cpp

View File

@ -32,8 +32,7 @@
| Release | IL2CPP | Mono |
| ------- | ------ | ---- |
| ML 0.4.0 | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Mono.zip) |
| ML 0.3.0 | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader_Legacy.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader_Legacy.Mono.zip) |
| ML 0.4+ | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Mono.zip) |
1. Take the `UnityExplorer.ML.[version].dll` file and put it in the `Mods\` folder created by MelonLoader.

Binary file not shown.

Binary file not shown.

View File

@ -121,16 +121,15 @@ namespace UnityExplorer.CSConsole
{
while (input.Length - 1 >= matchEndIdx)
{
matchEndIdx++;
if (IsNewLine(input[matchEndIdx]))
break;
matchEndIdx++;
}
}
// check caretIdx to determine inStringOrComment state
if (caretIdx >= match.startIndex && (caretIdx <= matchEndIdx || (caretIdx >= input.Length && matchEndIdx >= input.Length - 1)))
if (caretIdx >= match.startIndex && (caretIdx <= (matchEndIdx+1) || (caretIdx >= input.Length && matchEndIdx >= input.Length - 1)))
caretInStringOrComment = match.isStringOrComment;
}
// Append trailing unhighlighted input

View File

@ -7,12 +7,6 @@ using System.Text;
using UnityEngine;
using UnityExplorer.Core.Runtime;
/*
Welcome to the UnityExplorer C# Console!
Use the Help dropdown to see detailed examples of how to use this console.
To see your output, use the Log panel or a Console Log window.
*/
namespace UnityExplorer.CSConsole
{
public class ScriptInteraction : InteractiveBase
@ -76,4 +70,4 @@ namespace UnityExplorer.CSConsole
}
}
}
}

View File

@ -12,11 +12,11 @@ namespace UnityExplorer.CacheObject
public CacheConfigEntry(IConfigElement configElement)
{
this.RefConfigElement = configElement;
this.FallbackType = configElement.ElementType;
this.NameLabelText = $"<color=cyan>{configElement.Name}</color>" +
$"\r\n<color=grey><i>{configElement.Description}</i></color>";
this.FallbackType = configElement.ElementType;
this.NameLabelTextRaw = string.Empty;
configElement.OnValueChangedNotify += UpdateValueFromSource;
}

View File

@ -61,6 +61,7 @@ namespace UnityExplorer.CacheObject
var kvpCell = cell as CacheKeyValuePairCell;
kvpCell.NameLabel.text = $"{DictIndex}:";
kvpCell.HiddenNameLabel.Text = "";
kvpCell.Image.color = DictIndex % 2 == 0 ? CacheListEntryCell.EvenColor : CacheListEntryCell.OddColor;
if (KeyInputWanted)

View File

@ -28,6 +28,7 @@ namespace UnityExplorer.CacheObject
var listCell = cell as CacheListEntryCell;
listCell.NameLabel.text = $"{ListIndex}:";
listCell.HiddenNameLabel.Text = "";
listCell.Image.color = ListIndex % 2 == 0 ? CacheListEntryCell.EvenColor : CacheListEntryCell.OddColor;
}

View File

@ -34,6 +34,7 @@ namespace UnityExplorer.CacheObject
this.Owner = inspector;
this.NameLabelText = SignatureHighlighter.Parse(member.DeclaringType, false, member);
this.NameForFiltering = $"{member.DeclaringType.Name}.{member.Name}";
this.NameLabelTextRaw = NameForFiltering;
}
public override void ReleasePooledObjects()

View File

@ -30,15 +30,14 @@ namespace UnityExplorer.CacheObject
try
{
var methodInfo = MethodInfo;
if (methodInfo.IsGenericMethod)
methodInfo = MethodInfo.MakeGenericMethod(Evaluator.TryParseGenericArguments());
if (Arguments.Length > 0)
return methodInfo.Invoke(DeclaringInstance, Evaluator.TryParseArguments());
var ret = methodInfo.Invoke(DeclaringInstance, ArgumentUtility.EmptyArgs);
object ret;
if (HasArguments)
ret = methodInfo.Invoke(DeclaringInstance, Evaluator.TryParseArguments());
else
ret = methodInfo.Invoke(DeclaringInstance, ArgumentUtility.EmptyArgs);
HadException = false;
LastException = null;
return ret;

View File

@ -47,6 +47,7 @@ namespace UnityExplorer.CacheObject
public bool SubContentShowWanted { get; private set; }
public string NameLabelText { get; protected set; }
public string NameLabelTextRaw { get; protected set; }
public string ValueLabelText { get; protected set; }
public abstract bool ShouldAutoEvaluate { get; }
@ -260,6 +261,8 @@ namespace UnityExplorer.CacheObject
public virtual void SetDataToCell(CacheObjectCell cell)
{
cell.NameLabel.text = NameLabelText;
if (cell.HiddenNameLabel != null)
cell.HiddenNameLabel.Text = NameLabelTextRaw;
cell.ValueLabel.gameObject.SetActive(true);
cell.SubContentHolder.gameObject.SetActive(SubContentShowWanted);

View File

@ -45,6 +45,7 @@ namespace UnityExplorer.CacheObject.Views
public LayoutElement RightGroupLayout;
public Text NameLabel;
public InputFieldRef HiddenNameLabel;
public Text TypeLabel;
public Text ValueLabel;
public Toggle Toggle;
@ -116,8 +117,19 @@ namespace UnityExplorer.CacheObject.Views
NameLabel = UIFactory.CreateLabel(horiRow, "NameLabel", "<notset>", TextAnchor.MiddleLeft);
NameLabel.horizontalOverflow = HorizontalWrapMode.Wrap;
UIFactory.SetLayoutElement(NameLabel.gameObject, minHeight: 25, minWidth: 20, flexibleHeight: 300, flexibleWidth: 0);
NameLayout = NameLabel.GetComponent<LayoutElement>();
NameLayout = UIFactory.SetLayoutElement(NameLabel.gameObject, minHeight: 25, minWidth: 20, flexibleHeight: 300, flexibleWidth: 0);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(NameLabel.gameObject, true, true, true, true);
HiddenNameLabel = UIFactory.CreateInputField(NameLabel.gameObject, "HiddenNameLabel", "");
var hiddenRect = HiddenNameLabel.Component.GetComponent<RectTransform>();
hiddenRect.anchorMin = Vector2.zero;
hiddenRect.anchorMax = Vector2.one;
HiddenNameLabel.Component.readOnly = true;
HiddenNameLabel.Component.lineType = UnityEngine.UI.InputField.LineType.MultiLineNewline;
HiddenNameLabel.Component.textComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
HiddenNameLabel.Component.gameObject.GetComponent<Image>().color = Color.clear;
HiddenNameLabel.Component.textComponent.color = Color.clear;
UIFactory.SetLayoutElement(HiddenNameLabel.Component.gameObject, minHeight: 25, minWidth: 20, flexibleHeight: 300, flexibleWidth: 0);
// Right vertical group

View File

@ -116,7 +116,7 @@ namespace UnityExplorer.Core.Config
Reflection_Signature_Blacklist = new ConfigElement<string>("Member Signature Blacklist",
"Use this to blacklist certain member signatures if they are known to cause a crash or other issues.\r\n" +
"Seperate signatures with a semicolon ';'.\r\n" +
"For example, to blacklist Camera.main, you would add 'Camera.main;'",
"For example, to blacklist Camera.main, you would add 'UnityEngine.Camera.main;'",
"");
// Internal configs (panel save data)

View File

@ -1,4 +1,5 @@
using System;
using HarmonyLib;
using System;
using System.Collections;
using UnityEngine;
using UnityEngine.EventSystems;
@ -6,8 +7,6 @@ using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Input;
using UnityExplorer.UI;
using BF = System.Reflection.BindingFlags;
namespace UnityExplorer.Core.Input
{
@ -149,18 +148,97 @@ namespace UnityExplorer.Core.Input
// Patches
private static void SetupPatches()
public static void SetupPatches()
{
try
{
ExplorerCore.Loader.SetupCursorPatches();
PrefixPropertySetter(typeof(Cursor),
"lockState",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_lockState))));
PrefixPropertySetter(typeof(Cursor),
"visible",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_visible))));
PrefixPropertySetter(typeof(EventSystem),
"current",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_set_current))));
PrefixMethod(typeof(EventSystem),
"SetSelectedGameObject",
// some games use a modified version of uGUI that includes this extra int argument on this method.
new Type[] { typeof(GameObject), typeof(BaseEventData), typeof(int) },
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_SetSelectedGameObject))),
// most games use these arguments, we'll use them as our "backup".
new Type[] { typeof(GameObject), typeof(BaseEventData) });
//// Not sure if this one is needed.
//PrefixMethod(typeof(PointerInputModule),
// "ClearSelection",
// new Type[0],
// new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_PointerInputModule_ClearSelection))));
}
catch (Exception ex)
{
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
}
}
private static void PrefixMethod(Type type, string method, Type[] arguments, HarmonyMethod prefix, Type[] backupArgs = null)
{
try
{
var methodInfo = type.GetMethod(method, ReflectionUtility.FLAGS, null, arguments, null);
if (methodInfo == null)
{
if (backupArgs != null)
methodInfo = type.GetMethod(method, ReflectionUtility.FLAGS, null, backupArgs, null);
if (methodInfo == null)
throw new MissingMethodException($"Could not find method for patching - '{type.FullName}.{method}'!");
}
var processor = ExplorerCore.Harmony.CreateProcessor(methodInfo);
processor.AddPrefix(prefix);
processor.Patch();
}
catch (Exception e)
{
ExplorerCore.Log($"Exception setting up Cursor patches: {e.GetType()}, {e.Message}");
ExplorerCore.LogWarning($"Unable to patch {type.Name}.{method}: {e.Message}");
}
}
private static void PrefixPropertySetter(Type type, string property, HarmonyMethod prefix)
{
try
{
var processor = ExplorerCore.Harmony.CreateProcessor(type.GetProperty(property, ReflectionUtility.FLAGS).GetSetMethod());
processor.AddPrefix(prefix);
processor.Patch();
}
catch (Exception e)
{
ExplorerCore.Log($"Unable to patch {type.Name}.set_{property}: {e.Message}");
}
}
// Prevent setting non-UnityExplorer objects as selected when menu is open
public static bool Prefix_EventSystem_SetSelectedGameObject(GameObject __0)
{
if (!UIManager.ShowMenu || !UIManager.CanvasRoot)
return true;
return __0 && __0.transform.root.gameObject.GetInstanceID() == UIManager.CanvasRoot.GetInstanceID();
}
//public static bool Prefix_PointerInputModule_ClearSelection()
//{
// return !(UIManager.ShowMenu && UIManager.CanvasRoot);
//}
// Force EventSystem.current to be UnityExplorer's when menu is open
public static void Prefix_EventSystem_set_current(ref EventSystem value)
{
if (!settingEventSystem && value)

View File

@ -12,6 +12,8 @@ namespace UnityExplorer.Core.Input
{
public InputSystem()
{
SetupSupportedDevices();
m_kbCurrentProp = TKeyboard.GetProperty("current");
m_kbIndexer = TKeyboard.GetProperty("Item", new Type[] { TKey });
@ -32,7 +34,37 @@ namespace UnityExplorer.Core.Input
.GetMethod("ReadValue");
}
#region reflection cache
internal static void SetupSupportedDevices()
{
try
{
// typeof(InputSystem)
Type TInputSystem = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputSystem");
// InputSystem.settings
var settings = TInputSystem.GetProperty("settings", BindingFlags.Public | BindingFlags.Static).GetValue(null, null);
// typeof(InputSettings)
Type TSettings = settings.GetActualType();
// InputSettings.supportedDevices
PropertyInfo supportedProp = TSettings.GetProperty("supportedDevices", BindingFlags.Public | BindingFlags.Instance);
var supportedDevices = supportedProp.GetValue(settings, null);
// An empty supportedDevices list means all devices are supported.
#if CPP
// weird hack for il2cpp, use the implicit operator and cast Il2CppStringArray to ReadOnlyArray<string>
var args = new object[] { new UnhollowerBaseLib.Il2CppStringArray(0) };
var method = supportedDevices.GetActualType().GetMethod("op_Implicit", BindingFlags.Static | BindingFlags.Public);
supportedProp.SetValue(settings, method.Invoke(null, args), null);
#else
supportedProp.SetValue(settings, Activator.CreateInstance(supportedDevices.GetActualType(), new object[] { new string[0] }), null);
#endif
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting up InputSystem.settings.supportedDevices list!");
ExplorerCore.Log(ex);
}
}
#region reflection cache
public static Type TKeyboard => m_tKeyboard ?? (m_tKeyboard = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Keyboard"));
private static Type m_tKeyboard;
@ -73,7 +105,7 @@ namespace UnityExplorer.Core.Input
private static object m_scrollInfo;
private static PropertyInfo m_scrollDeltaProp;
#endregion
#endregion
public Vector2 MousePosition
{
@ -138,6 +170,8 @@ namespace UnityExplorer.Core.Input
public bool GetMouseButtonDown(int btn)
{
if (CurrentMouse == null)
return false;
switch (btn)
{
case 0: return (bool)m_btnWasPressedProp.GetValue(LeftMouseButton, null);
@ -149,6 +183,8 @@ namespace UnityExplorer.Core.Input
public bool GetMouseButton(int btn)
{
if (CurrentMouse == null)
return false;
switch (btn)
{
case 0: return (bool)m_btnIsPressedProp.GetValue(LeftMouseButton, null);

View File

@ -134,6 +134,9 @@ namespace UnityExplorer
var type = obj.GetType();
try
{
if (type.IsGenericType)
return type;
if (IsString(obj))
return typeof(string);
@ -178,7 +181,8 @@ namespace UnityExplorer
if (fullname.StartsWith("System."))
fullname = $"Il2Cpp{fullname}";
AllTypes.TryGetValue(fullname, out Type monoType);
if (!AllTypes.TryGetValue(fullname, out Type monoType))
ExplorerCore.LogWarning($"Failed to get type by name '{fullname}'!");
return monoType;
}
@ -477,59 +481,38 @@ namespace UnityExplorer
#region Force-loading game modules
internal static string UnhollowedFolderPath => Path.GetFullPath(
#if ML
Path.Combine("MelonLoader", "Managed")
#elif BIE
Path.Combine(BepInEx.Paths.BepInExRootPath, "unhollowed")
#else
Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Modules")
#endif
);
// Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded.
// Force loading all il2cpp modules
internal void TryLoadGameModules()
{
if (Directory.Exists(UnhollowedFolderPath))
var dir = ExplorerCore.Loader.UnhollowedModulesFolder;
if (Directory.Exists(dir))
{
var files = Directory.GetFiles(UnhollowedFolderPath);
foreach (var filePath in files)
{
try
{
DoLoadModule(filePath, true);
}
catch //(Exception ex)
{
//ExplorerCore.LogWarning($"Failed to force-load module '{name}': {ex.ReflectionExToString()}");
}
}
foreach (var filePath in Directory.GetFiles(dir, "*.dll"))
DoLoadModule(filePath);
}
else
ExplorerCore.LogWarning($"Expected Unhollowed folder path does not exist: '{UnhollowedFolderPath}'");
ExplorerCore.LogWarning($"Expected Unhollowed folder path does not exist: '{dir}'. " +
$"If you are using the standalone release, you can specify the Unhollowed modules path when you call CreateInstance().");
}
internal bool DoLoadModule(string fullPath, bool suppressWarning = false)
internal bool DoLoadModule(string fullPath)
{
if (!File.Exists(fullPath))
if (string.IsNullOrEmpty(fullPath) || !File.Exists(fullPath))
return false;
try
{
Assembly.LoadFile(fullPath);
//Assembly.Load(File.ReadAllBytes(fullPath));
return true;
}
catch (Exception e)
catch //(Exception e)
{
if (!suppressWarning)
Console.WriteLine($"Failed loading module '{Path.GetFileName(fullPath)}'! {e.ReflectionExToString()}");
//ExplorerCore.LogWarning($"Failed loading module '{Path.GetFileName(fullPath)}'! {e.ReflectionExToString()}");
return false;
}
return false;
}
#endregion
@ -799,23 +782,24 @@ namespace UnityExplorer
// Some ugly reflection to use the il2cpp interface for the instance type
var type = list.GetType();
var type = list.GetActualType();
var key = type.AssemblyQualifiedName;
if (!getEnumeratorMethods.ContainsKey(key))
{
var method = type.GetMethod("System_Collections_IEnumerable_GetEnumerator", FLAGS)
?? type.GetMethod("GetEnumerator");
var method = type.GetMethod("GetEnumerator")
?? type.GetMethod("System_Collections_IEnumerable_GetEnumerator", FLAGS);
getEnumeratorMethods.Add(key, method);
// ensure the enumerator type is supported
try
{
var test = getEnumeratorMethods[key].Invoke(list, null);
test.GetType().GetMethod("MoveNext").Invoke(test, null);
test.GetActualType().GetMethod("MoveNext").Invoke(test, null);
}
catch
catch (Exception ex)
{
ExplorerCore.Log($"IEnumerable failed to enumerate: {ex}");
notSupportedTypes.Add(key);
}
}
@ -824,7 +808,7 @@ namespace UnityExplorer
throw new NotSupportedException($"The IEnumerable type '{type.FullName}' does not support MoveNext.");
cppEnumerator = getEnumeratorMethods[key].Invoke(list, null);
var enumeratorType = cppEnumerator.GetType();
var enumeratorType = cppEnumerator.GetActualType();
var enumInfoKey = enumeratorType.AssemblyQualifiedName;
@ -878,7 +862,7 @@ namespace UnityExplorer
try
{
var type = dictionary.GetType();
var type = dictionary.GetActualType();
if (typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(type))
{
@ -888,22 +872,23 @@ namespace UnityExplorer
var keys = type.GetProperty("Keys").GetValue(dictionary, null);
var keyCollType = keys.GetType();
var cacheKey = keys.GetType().AssemblyQualifiedName;
var keyCollType = keys.GetActualType();
var cacheKey = keyCollType.AssemblyQualifiedName;
if (!getEnumeratorMethods.ContainsKey(cacheKey))
{
var method = keyCollType.GetMethod("System_Collections_IDictionary_GetEnumerator", FLAGS)
?? keyCollType.GetMethod("GetEnumerator");
var method = keyCollType.GetMethod("GetEnumerator")
?? keyCollType.GetMethod("System_Collections_IDictionary_GetEnumerator", FLAGS);
getEnumeratorMethods.Add(cacheKey, method);
// test support
try
{
var test = getEnumeratorMethods[cacheKey].Invoke(keys, null);
test.GetType().GetMethod("MoveNext").Invoke(test, null);
test.GetActualType().GetMethod("MoveNext").Invoke(test, null);
}
catch
catch (Exception ex)
{
ExplorerCore.Log($"IDictionary failed to enumerate: {ex}");
notSupportedTypes.Add(cacheKey);
}
}
@ -914,16 +899,16 @@ namespace UnityExplorer
var keyEnumerator = getEnumeratorMethods[cacheKey].Invoke(keys, null);
var keyInfo = new EnumeratorInfo
{
current = keyEnumerator.GetType().GetProperty("Current"),
moveNext = keyEnumerator.GetType().GetMethod("MoveNext"),
current = keyEnumerator.GetActualType().GetProperty("Current"),
moveNext = keyEnumerator.GetActualType().GetMethod("MoveNext"),
};
var values = type.GetProperty("Values").GetValue(dictionary, null);
var valueEnumerator = values.GetType().GetMethod("GetEnumerator").Invoke(values, null);
var valueEnumerator = values.GetActualType().GetMethod("GetEnumerator").Invoke(values, null);
var valueInfo = new EnumeratorInfo
{
current = valueEnumerator.GetType().GetProperty("Current"),
moveNext = valueEnumerator.GetType().GetMethod("MoveNext"),
current = valueEnumerator.GetActualType().GetProperty("Current"),
moveNext = valueEnumerator.GetActualType().GetMethod("MoveNext"),
};
dictEnumerator = EnumerateCppDict(keyInfo, keyEnumerator, valueInfo, valueEnumerator);

View File

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using HarmonyLib;
namespace UnityExplorer
{
public static class ReflectionPatches
{
public static void Init()
{
try
{
var method = typeof(Assembly).GetMethod(nameof(Assembly.GetTypes), new Type[0]);
var processor = ExplorerCore.Harmony.CreateProcessor(method);
processor.AddFinalizer(typeof(ReflectionPatches).GetMethod(nameof(Assembly_GetTypes)));
processor.Patch();
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting up Reflection patch: {ex}");
}
}
private static readonly Type[] emptyTypes = new Type[0];
public static Exception Assembly_GetTypes(Assembly __instance, Exception __exception, ref Type[] __result)
{
if (__exception != null)
{
try
{
__result = __instance.GetExportedTypes();
}
catch (ReflectionTypeLoadException e)
{
try
{
__result = e.Types.Where(it => it != null).ToArray();
}
catch
{
__result = emptyTypes;
}
}
catch
{
__result = emptyTypes;
}
}
return null;
}
}
}

View File

@ -27,6 +27,8 @@ namespace UnityExplorer
new ReflectionUtility();
#endif
Instance.Initialize();
ReflectionPatches.Init();
}
protected virtual void Initialize()

View File

@ -32,16 +32,6 @@ namespace UnityExplorer
return new AssetBundle(ptr);
}
// static void UnloadAllAssetBundles(bool unloadAllObjects);
internal delegate void d_UnloadAllAssetBundles(bool unloadAllObjects);
public static void UnloadAllAssetBundles(bool unloadAllObjects)
{
var iCall = ICallManager.GetICall<d_UnloadAllAssetBundles>("UnityEngine.AssetBundle::UnloadAllAssetBundles");
iCall.Invoke(unloadAllObjects);
}
// ~~~~~~~~~~~~ Instance ~~~~~~~~~~~~
private readonly IntPtr m_bundlePtr = IntPtr.Zero;
@ -78,7 +68,8 @@ namespace UnityExplorer
return new UnityEngine.Object(ptr).TryCast<T>();
}
// public extern void Unload(bool unloadAllLoadedObjects);
// Unload(bool unloadAllLoadedObjects);
internal delegate void d_Unload(IntPtr _this, bool unloadAllLoadedObjects);
public void Unload(bool unloadAssets = true)

View File

@ -7,7 +7,6 @@ using System.Runtime.InteropServices;
namespace UnityExplorer.Core.Runtime.Il2Cpp
{
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")]
public static class ICallManager
{
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]

View File

@ -6,8 +6,8 @@ using System.Linq;
using UnhollowerBaseLib;
using UnityEngine;
// CREDIT HerpDerpenstine
// https://github.com/LavaGang/MelonLoader/blob/master/MelonLoader.Support.Il2Cpp/MelonCoroutines.cs
// Credit to HerpDerpenstine and knah
// https://github.com/LavaGang/MelonLoader/blob/master/SM_Il2Cpp/Coroutines.cs
namespace UnityExplorer.Core.Runtime.Il2Cpp
{

View File

@ -73,6 +73,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
return ScriptableObject.CreateInstance(Il2CppType.From(type));
}
// Pretty disgusting but couldn't figure out a cleaner way yet unfortunately
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
{
var il2cppList = new Il2CppSystem.Collections.Generic.List<RaycastResult>();
@ -97,15 +98,17 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
internal delegate IntPtr d_FindObjectsOfTypeAll(IntPtr type);
internal static readonly string[] findObjectsOfTypeAllSignatures = new[]
{
"UnityEngine.Resources::FindObjectsOfTypeAll",
"UnityEngine.ResourcesAPIInternal::FindObjectsOfTypeAll" // Unity 2020+ updated to this
};
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
{
var iCall = ICallManager.GetICallUnreliable<d_FindObjectsOfTypeAll>(new[]
{
"UnityEngine.Resources::FindObjectsOfTypeAll",
"UnityEngine.ResourcesAPIInternal::FindObjectsOfTypeAll" // Unity 2020+ updated to this
});
return new Il2CppReferenceArray<UnityEngine.Object>(iCall.Invoke(Il2CppType.From(type).Pointer));
return new Il2CppReferenceArray<UnityEngine.Object>(
ICallManager.GetICallUnreliable<d_FindObjectsOfTypeAll>(findObjectsOfTypeAllSignatures)
.Invoke(Il2CppType.From(type).Pointer));
}
// Scene.GetRootGameObjects();
@ -117,22 +120,17 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
if (!scene.isLoaded)
return new GameObject[0];
int handle = scene.handle;
if (handle == -1)
if (scene.handle == -1)
return new GameObject[0];
int count = GetRootCount(handle);
int count = GetRootCount(scene.handle);
if (count < 1)
return new GameObject[0];
var list = new Il2CppSystem.Collections.Generic.List<GameObject>(count);
var iCall = ICallManager.GetICall<d_GetRootGameObjects>("UnityEngine.SceneManagement.Scene::GetRootGameObjectsInternal");
iCall.Invoke(handle, list.Pointer);
iCall.Invoke(scene.handle, list.Pointer);
return list.ToArray();
}

View File

@ -38,19 +38,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
return new Il2CppStructArray<byte>(ptr);
}
// bool ImageConversion.LoadImage(this Texture2D tex, byte[] data, bool markNonReadable);
internal delegate bool d_LoadImage(IntPtr tex, IntPtr data, bool markNonReadable);
public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
{
var il2cppArray = (Il2CppStructArray<byte>)data;
var iCall = ICallManager.GetICall<d_LoadImage>("UnityEngine.ImageConversion::LoadImage");
return iCall.Invoke(tex.Pointer, il2cppArray.Pointer, markNonReadable);
}
// Sprite Sprite.Create
public override Sprite CreateSprite(Texture2D texture)

View File

@ -10,6 +10,7 @@ using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer;
namespace UnityExplorer.Core.Runtime.Mono
{
@ -18,7 +19,6 @@ namespace UnityExplorer.Core.Runtime.Mono
public override void Initialize()
{
ExplorerCore.Context = RuntimeContext.Mono;
//Reflection = new MonoReflection();
TextureUtil = new MonoTextureUtil();
}
@ -28,60 +28,35 @@ namespace UnityExplorer.Core.Runtime.Mono
}
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
{
ExplorerCore.LogUnity(condition, type);
}
=> ExplorerCore.LogUnity(condition, type);
public override void StartCoroutine(IEnumerator routine)
{
ExplorerBehaviour.Instance.StartCoroutine(routine);
}
public override void StartCoroutine(IEnumerator routine)
=> ExplorerBehaviour.Instance.StartCoroutine(routine);
public override void Update()
{
}
public override T AddComponent<T>(GameObject obj, Type type)
{
return (T)obj.AddComponent(type);
}
public override T AddComponent<T>(GameObject obj, Type type)
=> (T)obj.AddComponent(type);
public override ScriptableObject CreateScriptable(Type type)
{
return ScriptableObject.CreateInstance(type);
}
public override ScriptableObject CreateScriptable(Type type)
=> ScriptableObject.CreateInstance(type);
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
{
raycaster.Raycast(data, list);
}
=> raycaster.Raycast(data, list);
public override string LayerToName(int layer)
public override string LayerToName(int layer)
=> LayerMask.LayerToName(layer);
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
=> Resources.FindObjectsOfTypeAll(type);
//private static readonly FieldInfo fi_Scene_handle = typeof(Scene).GetField("m_Handle", ReflectionUtility.AllFlags);
public override GameObject[] GetRootGameObjects(Scene scene)
=> scene.isLoaded ? scene.GetRootGameObjects() : new GameObject[0];
//public override int GetSceneHandle(Scene scene)
//{
// return (int)fi_Scene_handle.GetValue(scene);
//}
public override GameObject[] GetRootGameObjects(Scene scene)
{
if (!scene.isLoaded)
return new GameObject[0];
return scene.GetRootGameObjects();
}
public override int GetRootCount(Scene scene)
{
return scene.rootCount;
}
public override int GetRootCount(Scene scene)
=> scene.rootCount;
public override void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
Color? disabled = null)
@ -103,59 +78,42 @@ namespace UnityExplorer.Core.Runtime.Mono
SetColorBlock(selectable, colors);
}
public override void SetColorBlock(Selectable selectable, ColorBlock colors)
{
selectable.colors = colors;
}
public override void SetColorBlock(Selectable selectable, ColorBlock colors)
=> selectable.colors = colors;
}
}
public static class MonoExtensions
{
// Helpers to use the same style of AddListener that IL2CPP uses.
public static void AddListener(this UnityEvent _event, Action listener)
{
_event.AddListener(new UnityAction(listener));
}
=> _event.AddListener(new UnityAction(listener));
public static void AddListener<T>(this UnityEvent<T> _event, Action<T> listener)
{
_event.AddListener(new UnityAction<T>(listener));
}
=> _event.AddListener(new UnityAction<T>(listener));
public static void RemoveListener(this UnityEvent _event, Action listener)
{
_event.RemoveListener(new UnityAction(listener));
}
=> _event.RemoveListener(new UnityAction(listener));
public static void RemoveListener<T>(this UnityEvent<T> _event, Action<T> listener)
{
_event.RemoveListener(new UnityAction<T>(listener));
}
=> _event.RemoveListener(new UnityAction<T>(listener));
public static void Clear(this StringBuilder sb)
{
sb.Remove(0, sb.Length);
}
// Doesn't exist in NET 3.5
private static PropertyInfo pi_childControlHeight;
public static void Clear(this StringBuilder sb)
=> sb.Remove(0, sb.Length);
// These properties don't exist in some earlier games, so null check before trying to set them.
public static void SetChildControlHeight(this HorizontalOrVerticalLayoutGroup group, bool value)
{
if (pi_childControlHeight == null)
pi_childControlHeight = group.GetType().GetProperty("childControlHeight");
=> ReflectionUtility.GetPropertyInfo(typeof(HorizontalOrVerticalLayoutGroup), "childControlHeight")
?.SetValue(group, value, null);
pi_childControlHeight?.SetValue(group, value, null);
}
private static PropertyInfo pi_childControlWidth;
public static void SetChildControlWidth(this HorizontalOrVerticalLayoutGroup group, bool value)
{
if (pi_childControlWidth == null)
pi_childControlWidth = group.GetType().GetProperty("childControlWidth");
pi_childControlWidth?.SetValue(group, value, null);
}
=> ReflectionUtility.GetPropertyInfo(typeof(HorizontalOrVerticalLayoutGroup), "childControlWidth")
?.SetValue(group, value, null);
}
#endif

View File

@ -12,41 +12,28 @@ namespace UnityExplorer.Core.Runtime.Mono
public class MonoTextureUtil : TextureUtilProvider
{
public override void Blit(Texture2D tex, RenderTexture rt)
{
Graphics.Blit(tex, rt);
}
=> Graphics.Blit(tex, rt);
public override Sprite CreateSprite(Texture2D texture)
{
return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
}
=> Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
{
return tex.LoadImage(data, markNonReadable);
}
//public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
// => tex.LoadImage(data, markNonReadable);
public override Texture2D NewTexture2D(int width, int height)
{
return new Texture2D(width, height);
}
=> new Texture2D(width, height);
public override byte[] EncodeToPNG(Texture2D tex)
{
return EncodeToPNGSafe(tex);
}
=> EncodeToPNGSafe(tex);
private static MethodInfo EncodeToPNGMethod => m_encodeToPNGMethod ?? GetEncodeToPNGMethod();
private static MethodInfo m_encodeToPNGMethod;
public static byte[] EncodeToPNGSafe(Texture2D tex)
{
var method = EncodeToPNGMethod;
if (method.IsStatic)
return (byte[])method.Invoke(null, new object[] { tex });
else
return (byte[])method.Invoke(tex, ArgumentUtility.EmptyArgs);
return EncodeToPNGMethod.IsStatic
? (byte[])EncodeToPNGMethod.Invoke(null, new object[] { tex })
: (byte[])EncodeToPNGMethod.Invoke(tex, ArgumentUtility.EmptyArgs);
}
private static MethodInfo GetEncodeToPNGMethod()

View File

@ -40,8 +40,6 @@ namespace UnityExplorer
public abstract void Update();
//public virtual bool IsReferenceEqual(object a, object b) => ReferenceEquals(a, b);
// Unity API handlers
public abstract T AddComponent<T>(GameObject obj, Type type) where T : Component;
@ -54,8 +52,6 @@ namespace UnityExplorer
public abstract void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list);
//public abstract int GetSceneHandle(Scene scene);
public abstract GameObject[] GetRootGameObjects(Scene scene);
public abstract int GetRootCount(Scene scene);

View File

@ -22,7 +22,7 @@ namespace UnityExplorer.Core.Runtime
public abstract void Blit(Texture2D tex, RenderTexture rt);
public abstract bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable);
//public abstract bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable);
public abstract Sprite CreateSprite(Texture2D texture);
@ -43,27 +43,22 @@ namespace UnityExplorer.Core.Runtime
}
}
public static bool LoadImage(Texture2D tex, string filePath, bool markNonReadable)
{
if (!File.Exists(filePath))
return false;
return Instance.LoadImage(tex, File.ReadAllBytes(filePath), markNonReadable);
}
//public static bool LoadImage(Texture2D tex, string filePath, bool markNonReadable)
//{
// if (!File.Exists(filePath))
// return false;
//
// return Instance.LoadImage(tex, File.ReadAllBytes(filePath), markNonReadable);
//}
public static Texture2D Copy(Texture2D orig, Rect rect)
{
Color[] pixels;
if (!IsReadable(orig))
orig = ForceReadTexture(orig);
pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
Color[] pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
Texture2D newTex = Instance.NewTexture2D((int)rect.width, (int)rect.height);
newTex.SetPixels(pixels);
return newTex;
}
@ -92,7 +87,7 @@ namespace UnityExplorer.Core.Runtime
}
catch (Exception e)
{
ExplorerCore.Log("Exception on ForceReadTexture: " + e.ToString());
ExplorerCore.Log($"Exception on ForceReadTexture: {e.ToString()}");
return default;
}
}
@ -103,13 +98,11 @@ namespace UnityExplorer.Core.Runtime
Directory.CreateDirectory(dir);
byte[] data;
string savepath = dir + @"\" + name + ".png";
string savepath = $@"{dir}\{name}.png";
// Make sure we can EncodeToPNG it.
if (tex.format != TextureFormat.ARGB32 || !IsReadable(tex))
{
tex = ForceReadTexture(tex);
}
if (isDTXnmNormal)
{
@ -120,13 +113,9 @@ namespace UnityExplorer.Core.Runtime
data = Instance.EncodeToPNG(tex);
if (data == null || !data.Any())
{
ExplorerCore.LogWarning("Couldn't get any data for the texture!");
}
else
{
File.WriteAllBytes(savepath, data);
}
}
// Converts DTXnm-format Normal Map to RGBA-format Normal Map.

View File

@ -14,112 +14,31 @@ 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)
static TestClass()
{
ExplorerCore.Log($"{s}, {f}, {vector.ToString()}, {date}, {quater.eulerAngles.ToString()}, {b}, {enumvalue}");
Init_Mono();
#if CPP
Init_IL2CPP();
#endif
}
public static List<int> AWritableList = new List<int> { 1, 2, 3, 4, 5 };
public static Dictionary<string, int> AWritableDict = new Dictionary<string, int> { { "one", 1 }, { "two", 2 } };
public static IEnumerable ANestedList = new List<List<List<string>>>
{
new List<List<string>>
{
new List<string>
{
"one",
"two",
"one",
"two",
"one",
"two",
"one",
"two",
"one",
"two",
"one",
"two",
"one",
"two",
"one",
"two",
},
new List<string>
{
"three",
"four",
}
},
new List<List<string>>
{
new List<string>
{
"five"
}
}
};
public static IDictionary ARandomDictionary = new Dictionary<object, object>
{
{ 1, 2 },
{ "one", "two" },
{ true, false },
{ new Vector3(0,1,2), new Vector3(1,2,3) },
{ CameraClearFlags.Depth, CameraClearFlags.Color },
{ "################################################\r\n##########", null },
{ "subdict", new Dictionary<object,object> { { "key", "value" } } }
};
public static Hashtable TestHashtable = new Hashtable
{
{ "one", "value" },
{ "two", "value" },
{ "three", "value" },
};
public const int ConstantInt = 5;
public static Color AColor = Color.magenta;
public static Color32 AColor32 = Color.red;
// Test enumerables
public static List<object> ListOfInts;
public static List<List<List<string>>> NestedList;
public static IDictionary MixedDictionary;
public static Hashtable Hashtable;
public static byte[] ByteArray = new byte[16];
public static string LongString = new string('#', 10000);
public static List<string> BigList = new List<string>(10000);
public static List<short> ABigList = new List<short>(10000);
// Test const behaviour (should be a readonly field)
public const int ConstantInt5 = 5;
// Testing other InteractiveValues
public static Color Color = Color.magenta;
public static Color32 Color32 = Color.red;
public static string ALongString = new string('#', 10000);
public static List<object> RandomList
{
@ -133,25 +52,7 @@ namespace UnityExplorer.Tests
}
}
private static void TestGeneric<T>()
{
ExplorerCore.Log("Test1 " + typeof(T).FullName);
}
private static void TestGenericClass<T>() where T : class
{
ExplorerCore.Log("Test2 " + typeof(T).FullName);
}
private static void TestComponent<T>() where T : Component
{
ExplorerCore.Log("Test3 " + typeof(T).FullName);
}
private static void TestStruct<T>() where T : struct
{
ExplorerCore.Log("Test3 " + typeof(T).FullName);
}
// Test methods
private static object GetRandomObject()
{
@ -165,109 +66,133 @@ namespace UnityExplorer.Tests
case 2: return true;
case 3: return "hello";
case 4: return 50.5f;
case 5: return UnityEngine.CameraClearFlags.Color;
case 6: return new List<string> { "sub list", "lol" };
case 5: return CameraClearFlags.Color;
case 6: return new List<string> { "one", "two" };
}
return ret;
}
public static void TestComponent<T>() where T : Component
{
ExplorerCore.Log($"Test3 {typeof(T).FullName}");
}
public static void TestArgumentParse(string s, int i, Color color, CameraClearFlags flags, Vector3 vector, Quaternion quaternion)
{
ExplorerCore.Log($"{s}, {i}, {color.ToString()}, {flags}, {vector.ToString()}, {quaternion.ToString()}");
}
private static void Init_Mono()
{
ExplorerCore.Log($"1: Basic list");
ListOfInts = new List<object> { 1, 2, 3, 4, 5 };
ExplorerCore.Log($"2: Nested list");
NestedList = new List<List<List<string>>>
{
new List<List<string>> {
new List<string> { "1", "2", "3" },
new List<string> { "4", "5", "6" },
},
new List<List<string>>
{
new List<string> { "7", "8", "9" }
}
};
ExplorerCore.Log($"3: Dictionary");
MixedDictionary = new Dictionary<object, object>
{
{ 1, 2 },
{ "one", "two" },
{ true, false },
{ new Vector3(0,1,2), new Vector3(1,2,3) },
{ CameraClearFlags.Depth, CameraClearFlags.Color },
{ "################################################\r\n##########", null },
{ "subdict", new Dictionary<object,object> { { "key", "value" } } }
};
ExplorerCore.Log($"4: Hashtable");
Hashtable = new Hashtable { { "One", 1 }, { "Two", 2 } };
ExplorerCore.Log($"5: Big list");
for (int i = 0; i < ABigList.Capacity; i++)
ABigList.Add((short)UnityEngine.Random.Range(0, short.MaxValue));
ExplorerCore.Log("Finished TestClass Init_Mono");
}
#if CPP
public static Il2CppSystem.Collections.IList IL2CPP_IList;
public static Il2CppSystem.Collections.Generic.List<string> IL2CPP_ListString;
public static Il2CppSystem.Collections.Generic.HashSet<string> IL2CPP_HashSet;
public static Il2CppSystem.Collections.Generic.Dictionary<string, string> IL2CPP_Dict;
public static Il2CppSystem.Collections.Hashtable IL2CPP_HashTable;
public static Il2CppSystem.Collections.IDictionary IL2CPP_IDict;
public static string IL2CPP_systemString = "Test";
public static Il2CppSystem.Object IL2CPP_objectString = "string boxed as cpp object";
public static Il2CppSystem.String IL2CPP_il2cppString = "string boxed as cpp string";
public static string nullString = null;
public static List<Il2CppSystem.Object> IL2CPP_listOfBoxedObjects;
public static Il2CppStructArray<int> IL2CPP_structArray;
public static Il2CppStringArray IL2CPP_stringArray;
public static Il2CppReferenceArray<Il2CppSystem.Object> IL2CPP_ReferenceArray;
public static Il2CppSystem.Collections.IDictionary IL2CPP_IDict;
public static Il2CppSystem.Collections.IList IL2CPP_IList;
public static Dictionary<Il2CppSystem.String, Il2CppSystem.Object> CppBoxedDict;
public static Il2CppSystem.Collections.Generic.HashSet<string> IL2CPP_HashSet;
public static Il2CppSystem.Collections.Generic.Dictionary<string, string> IL2CPP_Dict;
public static Il2CppSystem.Collections.Hashtable IL2CPP_HashTable;
public static Il2CppSystem.Object cppBoxedInt;
public static Il2CppSystem.Int32 cppInt;
public static Il2CppSystem.Decimal cppDecimal;
public static Il2CppSystem.Object cppDecimalBoxed;
public static Il2CppSystem.Object cppVector3Boxed;
public static string IL2CPP_systemString = "Test";
public static Il2CppSystem.Object IL2CPP_objectString = "string boxed as cpp object";
public static Il2CppSystem.String IL2CPP_il2cppString = "string boxed as cpp string";
public static string nullString = null;
public static Il2CppSystem.Object RandomBoxedColor
private static void Init_IL2CPP()
{
get
{
int ran = UnityEngine.Random.Range(0, 3);
switch (ran)
{
case 1: return new Color32().BoxIl2CppObject();
case 2: return Color.magenta.BoxIl2CppObject();
default:
return null;
}
}
}
public static Il2CppSystem.Collections.Hashtable cppHashset;
public static Dictionary<Il2CppSystem.String, Il2CppSystem.Object> CppBoxedDict;
#endif
static TestClass()
{
for (int i = 0; i < BigList.Capacity; i++)
BigList.Add(i.ToString());
#if CPP
ExplorerCore.Log($"IL2CPP 1: Il2Cpp Dictionary<string, string>");
IL2CPP_Dict = new Il2CppSystem.Collections.Generic.Dictionary<string, string>();
IL2CPP_Dict.Add("key1", "value1");
IL2CPP_Dict.Add("key2", "value2");
IL2CPP_Dict.Add("key3", "value3");
ExplorerCore.Log($"IL2CPP 2: Il2Cpp Hashtable");
IL2CPP_HashTable = new Il2CppSystem.Collections.Hashtable();
IL2CPP_HashTable.Add("key1", "value1");
IL2CPP_HashTable.Add("key2", "value2");
IL2CPP_HashTable.Add("key3", "value3");
ExplorerCore.Log($"IL2CPP 3: Il2Cpp IDictionary");
var dict2 = new Il2CppSystem.Collections.Generic.Dictionary<string, string>();
dict2.Add("key1", "value1");
IL2CPP_IDict = dict2.TryCast<Il2CppSystem.Collections.IDictionary>();
ExplorerCore.Log($"IL2CPP 4: Il2Cpp List of Il2Cpp Object");
var list = new Il2CppSystem.Collections.Generic.List<Il2CppSystem.Object>(5);
list.Add("one");
list.Add("two");
IL2CPP_IList = list.TryCast<Il2CppSystem.Collections.IList>();
ExplorerCore.Log($"IL2CPP 5: Il2Cpp List of strings");
IL2CPP_ListString = new Il2CppSystem.Collections.Generic.List<string>();
IL2CPP_ListString.Add("hello,");
IL2CPP_ListString.Add("world!");
ExplorerCore.Log($"IL2CPP 6: Il2Cpp HashSet of strings");
IL2CPP_HashSet = new Il2CppSystem.Collections.Generic.HashSet<string>();
IL2CPP_HashSet.Add("one");
IL2CPP_HashSet.Add("two");
ExplorerCore.Log($"IL2CPP 7: Dictionary of Il2Cpp String and Il2Cpp Object");
CppBoxedDict = new Dictionary<Il2CppSystem.String, Il2CppSystem.Object>();
CppBoxedDict.Add("1", new Il2CppSystem.Int32 { m_value = 1 }.BoxIl2CppObject());
CppBoxedDict.Add("2", new Il2CppSystem.Int32 { m_value = 2 }.BoxIl2CppObject());
CppBoxedDict.Add("3", new Il2CppSystem.Int32 { m_value = 3 }.BoxIl2CppObject());
CppBoxedDict.Add("4", new Il2CppSystem.Int32 { m_value = 4 }.BoxIl2CppObject());
cppDecimal = new Il2CppSystem.Decimal(1f);
cppDecimalBoxed = new Il2CppSystem.Decimal(1f).BoxIl2CppObject();
cppVector3Boxed = Vector3.down.BoxIl2CppObject();
ExplorerCore.Log($"IL2CPP 8: List of boxed Il2Cpp Objects");
IL2CPP_listOfBoxedObjects = new List<Il2CppSystem.Object>();
IL2CPP_listOfBoxedObjects.Add((Il2CppSystem.String)"boxedString");
IL2CPP_listOfBoxedObjects.Add(new Il2CppSystem.Int32 { m_value = 5 }.BoxIl2CppObject());
IL2CPP_listOfBoxedObjects.Add(Color.red.BoxIl2CppObject());
// boxed enum test
try
{
var cppType = Il2CppType.Of<CameraClearFlags>();
@ -283,9 +208,10 @@ namespace UnityExplorer.Tests
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Test fail: {ex}");
ExplorerCore.LogWarning($"Boxed enum test fail: {ex}");
}
ExplorerCore.Log($"IL2CPP 9: Il2Cpp struct array of ints");
IL2CPP_structArray = new UnhollowerBaseLib.Il2CppStructArray<int>(5);
IL2CPP_structArray[0] = 0;
IL2CPP_structArray[1] = 1;
@ -293,24 +219,21 @@ namespace UnityExplorer.Tests
IL2CPP_structArray[3] = 3;
IL2CPP_structArray[4] = 4;
IL2CPP_stringArray = new UnhollowerBaseLib.Il2CppStringArray(2);
IL2CPP_stringArray[0] = "hello, ";
IL2CPP_stringArray[1] = "world!";
ExplorerCore.Log($"IL2CPP 10: Il2Cpp reference array of boxed objects");
IL2CPP_ReferenceArray = new UnhollowerBaseLib.Il2CppReferenceArray<Il2CppSystem.Object>(3);
IL2CPP_ReferenceArray[0] = new Il2CppSystem.Int32 { m_value = 5 }.BoxIl2CppObject();
IL2CPP_ReferenceArray[1] = null;
IL2CPP_ReferenceArray[2] = (Il2CppSystem.String)"whats up";
ExplorerCore.Log($"IL2CPP 11: Misc il2cpp members");
cppBoxedInt = new Il2CppSystem.Int32() { m_value = 5 }.BoxIl2CppObject();
cppInt = new Il2CppSystem.Int32 { m_value = 420 };
cppDecimal = new Il2CppSystem.Decimal(1f);
cppDecimalBoxed = new Il2CppSystem.Decimal(1f).BoxIl2CppObject();
cppVector3Boxed = Vector3.down.BoxIl2CppObject();
cppHashset = new Il2CppSystem.Collections.Hashtable();
cppHashset.Add("key1", "itemOne");
cppHashset.Add("key2", "itemTwo");
cppHashset.Add("key3", "itemThree");
#endif
ExplorerCore.Log($"Finished Init_Il2Cpp");
}
#endif
}
}

View File

@ -13,7 +13,7 @@ namespace UnityExplorer
/// </summary>
public static bool ContainsIgnoreCase(this string _this, string s)
{
return ParseUtility.en_US.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0;
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0;
}
/// <summary>

View File

@ -10,8 +10,6 @@ namespace UnityExplorer
{
public static class ParseUtility
{
public static CultureInfo en_US = new CultureInfo("en-US");
private static readonly HashSet<Type> nonPrimitiveTypes = new HashSet<Type>
{
typeof(string),
@ -19,20 +17,18 @@ namespace UnityExplorer
typeof(DateTime),
};
public const string NUMBER_FORMAT = "0.####";
// Helper for formatting float/double/decimal numbers to maximum of 4 decimal points.
// And also for formatting a sequence of those numbers, ie a Vector3, Color etc
public static readonly string NumberFormatString = $"0.####";
private static readonly Dictionary<int, string> numSequenceStrings = new Dictionary<int, string>();
// Helper for formatting float/double/decimal numbers to maximum of 4 decimal points.
public static string FormatDecimalSequence(params object[] numbers)
{
if (numbers.Length <= 0)
return null;
int count = numbers.Length;
var formatString = GetSequenceFormatString(count);
return string.Format(en_US, formatString, numbers);
return string.Format(CultureInfo.CurrentCulture, GetSequenceFormatString(numbers.Length), numbers);
}
public static string GetSequenceFormatString(int count)
@ -46,19 +42,19 @@ namespace UnityExplorer
string[] strings = new string[count];
for (int i = 0; i < count; i++)
strings[i] = $"{{{i}:{NUMBER_FORMAT}}}";
strings[i] = $"{{{i}:{NumberFormatString}}}";
string s = string.Join(", ", strings);
numSequenceStrings.Add(count, s);
return s;
string ret = string.Join(" ", strings);
numSequenceStrings.Add(count, ret);
return ret;
}
// Main parsing API
public static bool CanParse(Type type)
{
if (string.IsNullOrEmpty(type.FullName))
return false;
return type.IsPrimitive || type.IsEnum || nonPrimitiveTypes.Contains(type) || customTypes.ContainsKey(type.FullName);
return !string.IsNullOrEmpty(type?.FullName)
&& (type.IsPrimitive || type.IsEnum || nonPrimitiveTypes.Contains(type) || customTypes.ContainsKey(type.FullName));
}
public static bool TryParse(string input, Type type, out object obj, out Exception parseException)
@ -143,7 +139,7 @@ namespace UnityExplorer
else if (formattedTypes.Contains(type))
{
return ReflectionUtility.GetMethodInfo(type, "ToString", new Type[] { typeof(string), typeof(IFormatProvider) })
.Invoke(obj, new object[] { NUMBER_FORMAT, en_US })
.Invoke(obj, new object[] { NumberFormatString, CultureInfo.CurrentCulture })
as string;
}
else
@ -166,9 +162,7 @@ namespace UnityExplorer
try
{
if (type.IsEnum)
{
typeInputExamples.Add(type.AssemblyQualifiedName, Enum.GetNames(type).First());
}
else
{
var instance = Activator.CreateInstance(type);
@ -222,10 +216,10 @@ namespace UnityExplorer
{
Vector2 vector = default;
var split = input.Split(',');
var split = input.Split(' ');
vector.x = float.Parse(split[0].Trim(), en_US);
vector.y = float.Parse(split[1].Trim(), en_US);
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
return vector;
}
@ -244,11 +238,11 @@ namespace UnityExplorer
{
Vector3 vector = default;
var split = input.Split(',');
var split = input.Split(' ');
vector.x = float.Parse(split[0].Trim(), en_US);
vector.y = float.Parse(split[1].Trim(), en_US);
vector.z = float.Parse(split[2].Trim(), en_US);
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
return vector;
}
@ -267,12 +261,12 @@ namespace UnityExplorer
{
Vector4 vector = default;
var split = input.Split(',');
var split = input.Split(' ');
vector.x = float.Parse(split[0].Trim(), en_US);
vector.y = float.Parse(split[1].Trim(), en_US);
vector.z = float.Parse(split[2].Trim(), en_US);
vector.w = float.Parse(split[3].Trim(), en_US);
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
vector.w = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
return vector;
}
@ -291,22 +285,22 @@ namespace UnityExplorer
{
Vector3 vector = default;
var split = input.Split(',');
var split = input.Split(' ');
if (split.Length == 4)
{
Quaternion quat = default;
quat.x = float.Parse(split[0].Trim(), en_US);
quat.y = float.Parse(split[1].Trim(), en_US);
quat.z = float.Parse(split[2].Trim(), en_US);
quat.w = float.Parse(split[3].Trim(), en_US);
quat.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
quat.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
quat.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
quat.w = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
return quat;
}
else
{
vector.x = float.Parse(split[0].Trim(), en_US);
vector.y = float.Parse(split[1].Trim(), en_US);
vector.z = float.Parse(split[2].Trim(), en_US);
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
return Quaternion.Euler(vector);
}
}
@ -327,12 +321,12 @@ namespace UnityExplorer
{
Rect rect = default;
var split = input.Split(',');
var split = input.Split(' ');
rect.x = float.Parse(split[0].Trim(), en_US);
rect.y = float.Parse(split[1].Trim(), en_US);
rect.width = float.Parse(split[2].Trim(), en_US);
rect.height = float.Parse(split[3].Trim(), en_US);
rect.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
rect.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
rect.width = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
rect.height = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
return rect;
}
@ -351,13 +345,13 @@ namespace UnityExplorer
{
Color color = default;
var split = input.Split(',');
var split = input.Split(' ');
color.r = float.Parse(split[0].Trim(), en_US);
color.g = float.Parse(split[1].Trim(), en_US);
color.b = float.Parse(split[2].Trim(), en_US);
color.r = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
color.g = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
color.b = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
if (split.Length > 3)
color.a = float.Parse(split[3].Trim(), en_US);
color.a = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
else
color.a = 1;
@ -378,13 +372,13 @@ namespace UnityExplorer
{
Color32 color = default;
var split = input.Split(',');
var split = input.Split(' ');
color.r = byte.Parse(split[0].Trim(), en_US);
color.g = byte.Parse(split[1].Trim(), en_US);
color.b = byte.Parse(split[2].Trim(), en_US);
color.r = byte.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
color.g = byte.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
color.b = byte.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
if (split.Length > 3)
color.a = byte.Parse(split[3].Trim(), en_US);
color.a = byte.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
else
color.a = 255;
@ -397,7 +391,7 @@ namespace UnityExplorer
return null;
// ints, this is fine
return $"{color.r}, {color.g}, {color.b}, {color.a}";
return $"{color.r} {color.g} {color.b} {color.a}";
}
// Layermask (Int32)

View File

@ -20,13 +20,15 @@ namespace UnityExplorer
public static class ExplorerCore
{
public const string NAME = "UnityExplorer";
public const string VERSION = "4.1.8";
public const string VERSION = "4.2.1";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer";
public static IExplorerLoader Loader { get; private set; }
public static RuntimeContext Context { get; internal set; }
public static HarmonyLib.Harmony Harmony { get; } = new HarmonyLib.Harmony(GUID);
/// <summary>
/// Initialize UnityExplorer with the provided Loader implementation.
/// </summary>
@ -47,10 +49,9 @@ namespace UnityExplorer
Directory.CreateDirectory(Loader.ExplorerFolder);
ConfigManager.Init(Loader.ConfigHandler);
ReflectionUtility.Init();
RuntimeProvider.Init();
SceneHandler.Init();
InputManager.Init();

View File

@ -8,20 +8,10 @@
<InputAssemblies Include="..\lib\mcs-unity\mcs\bin\Release\mcs.dll" />
<InputAssemblies Include="packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll" />
</ItemGroup>
<!-- MonoMod for MelonLoader 0.3.0 -->
<ItemGroup Condition="'$(IsMelonLoaderLegacy)'=='true'">
<InputAssemblies Include="packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.dll" />
<InputAssemblies Include="packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Mdb.dll" />
<InputAssemblies Include="packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Pdb.dll" />
<InputAssemblies Include="packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Rocks.dll" />
<InputAssemblies Include="packages\MonoMod.RuntimeDetour.20.1.1.4\lib\net35\MonoMod.RuntimeDetour.dll" />
<InputAssemblies Include="packages\MonoMod.Utils.20.1.1.4\lib\net35\MonoMod.Utils.dll" />
</ItemGroup>
<!-- Required references for ILRepack -->
<ItemGroup>
<ReferenceFolders Include="packages\HarmonyX.2.4.2\lib\net35\" />
<ReferenceFolders Include="packages\HarmonyX.2.5.2\lib\net35\" />
<ReferenceFolders Include="..\lib\BepInEx.6.IL2CPP\" />
<ReferenceFolders Include="..\lib\BepInEx.6.Mono\" />
<ReferenceFolders Include="..\lib\BepInEx.5\" />

View File

@ -47,6 +47,7 @@ namespace UnityExplorer.Inspectors
public ScrollPool<CacheMemberCell> MemberScrollPool { get; private set; }
public InputFieldRef HiddenNameText;
public Text NameText;
public Text AssemblyText;
private Toggle autoUpdateToggle;
@ -125,6 +126,7 @@ namespace UnityExplorer.Inspectors
currentBaseTabText = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}";
Tab.TabText.text = currentBaseTabText;
NameText.text = SignatureHighlighter.Parse(TargetType, true);
HiddenNameText.Text = TargetType.FullName;
string asmText;
if (TargetType.Assembly is AssemblyBuilder || string.IsNullOrEmpty(TargetType.Assembly.Location))
@ -332,8 +334,27 @@ namespace UnityExplorer.Inspectors
// Class name, assembly
NameText = UIFactory.CreateLabel(UIRoot, "Title", "not set", TextAnchor.MiddleLeft, fontSize: 17);
UIFactory.SetLayoutElement(NameText.gameObject, minHeight: 25, flexibleHeight: 0);
var titleHolder = UIFactory.CreateUIObject("TitleHolder", UIRoot);
UIFactory.SetLayoutElement(titleHolder, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
NameText = UIFactory.CreateLabel(titleHolder, "VisibleTitle", "NotSet", TextAnchor.MiddleLeft);
var namerect = NameText.GetComponent<RectTransform>();
namerect.anchorMin = new Vector2(0, 0);
namerect.anchorMax = new Vector2(1, 1);
NameText.fontSize = 17;
UIFactory.SetLayoutElement(NameText.gameObject, minHeight: 35, flexibleHeight: 0, minWidth: 300, flexibleWidth: 9999);
HiddenNameText = UIFactory.CreateInputField(titleHolder, "Title", "not set");
var hiddenrect = HiddenNameText.Component.gameObject.GetComponent<RectTransform>();
hiddenrect.anchorMin = new Vector2(0, 0);
hiddenrect.anchorMax = new Vector2(1, 1);
HiddenNameText.Component.readOnly = true;
HiddenNameText.Component.lineType = InputField.LineType.MultiLineNewline;
HiddenNameText.Component.gameObject.GetComponent<Image>().color = Color.clear;
HiddenNameText.Component.textComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
HiddenNameText.Component.textComponent.fontSize = 17;
HiddenNameText.Component.textComponent.color = Color.clear;
UIFactory.SetLayoutElement(HiddenNameText.Component.gameObject, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
AssemblyText = UIFactory.CreateLabel(UIRoot, "AssemblyLabel", "not set", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(AssemblyText.gameObject, minHeight: 25, flexibleWidth: 9999);

View File

@ -39,6 +39,8 @@ namespace UnityExplorer
=> Log;
#endif
public string UnhollowedModulesFolder => Path.Combine(Paths.BepInExRootPath, "unhollowed");
public ConfigHandler ConfigHandler => _configHandler;
private BepInExConfigHandler _configHandler;
@ -51,8 +53,7 @@ namespace UnityExplorer
public Action<object> OnLogWarning => LogSource.LogWarning;
public Action<object> OnLogError => LogSource.LogError;
// Init common to Mono and Il2Cpp
internal void UniversalInit()
private void Init()
{
Instance = this;
_configHandler = new BepInExConfigHandler();
@ -62,57 +63,15 @@ namespace UnityExplorer
#if MONO // Mono
internal void Awake()
{
UniversalInit();
Init();
}
#else // Il2Cpp
public override void Load()
{
UniversalInit();
Init();
}
#endif
public void SetupCursorPatches()
{
try
{
this.HarmonyInstance.PatchAll();
}
catch (Exception ex)
{
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
}
}
[HarmonyPatch(typeof(EventSystem), "current", MethodType.Setter)]
public class PATCH_EventSystem_current
{
[HarmonyPrefix]
public static void Prefix_EventSystem_set_current(ref EventSystem value)
{
CursorUnlocker.Prefix_EventSystem_set_current(ref value);
}
}
[HarmonyPatch(typeof(Cursor), "lockState", MethodType.Setter)]
public class PATCH_Cursor_lockState
{
[HarmonyPrefix]
public static void Prefix_set_lockState(ref CursorLockMode value)
{
CursorUnlocker.Prefix_set_lockState(ref value);
}
}
[HarmonyPatch(typeof(Cursor), "visible", MethodType.Setter)]
public class PATCH_Cursor_visible
{
[HarmonyPrefix]
public static void Prefix_set_visible(ref bool value)
{
CursorUnlocker.Prefix_set_visible(ref value);
}
}
}
}
#endif

View File

@ -9,13 +9,12 @@ namespace UnityExplorer
public interface IExplorerLoader
{
string ExplorerFolder { get; }
string UnhollowedModulesFolder { get; }
ConfigHandler ConfigHandler { get; }
Action<object> OnLogMessage { get; }
Action<object> OnLogWarning { get; }
Action<object> OnLogError { get; }
void SetupCursorPatches();
}
}

View File

@ -1,86 +0,0 @@
#if ML
using System;
using System.IO;
using MelonLoader;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Input;
using UnityExplorer.Loader.ML;
#if ML_LEGACY
using Harmony;
#else
using HarmonyLib;
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.UNIVERSAL)]
#endif
[assembly: MelonInfo(typeof(ExplorerMelonMod), ExplorerCore.NAME, ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
[assembly: MelonGame(null, null)]
[assembly: MelonColor(ConsoleColor.DarkCyan)]
namespace UnityExplorer
{
public class ExplorerMelonMod : MelonMod, IExplorerLoader
{
public static ExplorerMelonMod Instance;
public string ExplorerFolder => Path.Combine("Mods", ExplorerCore.NAME);
public ConfigHandler ConfigHandler => _configHandler;
public MelonLoaderConfigHandler _configHandler;
public Action<object> OnLogMessage => MelonLogger.Msg;
public Action<object> OnLogWarning => MelonLogger.Warning;
public Action<object> OnLogError => MelonLogger.Error;
public override void OnApplicationStart()
{
Instance = this;
_configHandler = new MelonLoaderConfigHandler();
ExplorerCore.Init(this);
}
public void SetupCursorPatches()
{
try
{
PrefixProperty(typeof(Cursor),
"lockState",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_lockState))));
PrefixProperty(typeof(Cursor),
"visible",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_visible))));
PrefixProperty(typeof(EventSystem),
"current",
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_set_current))));
}
catch (Exception ex)
{
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
}
}
private void PrefixProperty(Type type, string property, HarmonyMethod prefix)
{
try
{
var prop = type.GetProperty(property);
#if ML_LEGACY
this.Harmony.Patch(prop.GetSetMethod(), prefix: prefix);
#else
HarmonyInstance.Patch(prop.GetSetMethod(), prefix: prefix);
#endif
}
catch (Exception e)
{
ExplorerCore.Log($"Unable to patch {type.Name}.set_{property}: {e.Message}");
}
}
}
}
#endif

View File

@ -1,206 +0,0 @@
#if ML
#if !ML_LEGACY // ML 0.3.1+ config handler
using MelonLoader;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
namespace UnityExplorer.Loader.ML
{
public class MelonLoaderConfigHandler : ConfigHandler
{
internal const string CTG_NAME = "UnityExplorer";
internal MelonPreferences_Category prefCategory;
public override void Init()
{
prefCategory = MelonPreferences.CreateCategory(CTG_NAME, $"{CTG_NAME} Settings", false, true);
}
public override void LoadConfig()
{
foreach (var entry in ConfigManager.ConfigElements)
{
var key = entry.Key;
if (prefCategory.GetEntry(key) is MelonPreferences_Entry)
{
var config = entry.Value;
config.BoxedValue = config.GetLoaderConfigValue();
}
}
}
public override void RegisterConfigElement<T>(ConfigElement<T> config)
{
var entry = prefCategory.CreateEntry(config.Name, config.Value, null, config.Description, config.IsInternal, false);
entry.OnValueChangedUntyped += () =>
{
if ((entry.Value == null && config.Value == null) || config.Value.Equals(entry.Value))
return;
config.Value = entry.Value;
};
}
public override void SetConfigValue<T>(ConfigElement<T> config, T value)
{
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
{
entry.Value = value;
//entry.Save();
}
}
public override T GetConfigValue<T>(ConfigElement<T> config)
{
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
return entry.Value;
return default;
}
public override void OnAnyConfigChanged()
{
}
public override void SaveConfig()
{
MelonPreferences.Save();
}
}
}
#else // ML 0.3.0 config handler
using MelonLoader;
using MelonLoader.Tomlyn.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
namespace UnityExplorer.Loader.ML
{
public class MelonLoaderConfigHandler : ConfigHandler
{
internal const string CTG_NAME = "UnityExplorer";
internal MelonPreferences_Category prefCategory;
public override void Init()
{
prefCategory = MelonPreferences.CreateCategory(CTG_NAME, $"{CTG_NAME} Settings");
try { MelonPreferences.Mapper.RegisterMapper(KeycodeReader, KeycodeWriter); } catch { }
try { MelonPreferences.Mapper.RegisterMapper(AnchorReader, AnchorWriter); } catch { }
}
public override void LoadConfig()
{
foreach (var entry in ConfigManager.ConfigElements)
{
var key = entry.Key;
if (prefCategory.GetEntry(key) is MelonPreferences_Entry)
{
var config = entry.Value;
config.BoxedValue = config.GetLoaderConfigValue();
}
}
}
public override void RegisterConfigElement<T>(ConfigElement<T> config)
{
var entry = prefCategory.CreateEntry(config.Name, config.Value, null, config.IsInternal) as MelonPreferences_Entry<T>;
entry.OnValueChangedUntyped += () =>
{
if ((entry.Value == null && config.Value == null) || config.Value.Equals(entry.Value))
return;
config.Value = entry.Value;
};
}
public override void SetConfigValue<T>(ConfigElement<T> config, T value)
{
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
{
entry.Value = value;
entry.Save();
}
}
public override T GetConfigValue<T>(ConfigElement<T> config)
{
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
return entry.Value;
return default;
}
public override void OnAnyConfigChanged()
{
}
public override void SaveConfig()
{
MelonPreferences.Save();
}
// Enum config handlers
public static KeyCode KeycodeReader(TomlObject value)
{
try
{
KeyCode kc = (KeyCode)Enum.Parse(typeof(KeyCode), (value as TomlString).Value);
if (kc == default)
throw new Exception();
return kc;
}
catch
{
return KeyCode.F7;
}
}
public static TomlObject KeycodeWriter(KeyCode value)
{
return MelonPreferences.Mapper.ToToml(value.ToString());
}
public static UI.UIManager.VerticalAnchor AnchorReader(TomlObject value)
{
try
{
return (UI.UIManager.VerticalAnchor)Enum.Parse(typeof(UI.UIManager.VerticalAnchor), (value as TomlString).Value);
}
catch
{
return UI.UIManager.VerticalAnchor.Top;
}
}
public static TomlObject AnchorWriter(UI.UIManager.VerticalAnchor anchor)
{
return MelonPreferences.Mapper.ToToml(anchor.ToString());
}
}
}
#endif
#endif

View File

@ -0,0 +1,47 @@
#if ML
using System;
using System.IO;
using MelonLoader;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Input;
using UnityExplorer.Loader.ML;
using HarmonyLib;
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.UNIVERSAL)]
[assembly: MelonInfo(typeof(ExplorerMelonMod), ExplorerCore.NAME, ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
[assembly: MelonGame(null, null)]
[assembly: MelonColor(ConsoleColor.DarkCyan)]
namespace UnityExplorer
{
public class ExplorerMelonMod : MelonMod, IExplorerLoader
{
public static ExplorerMelonMod Instance;
public string ExplorerFolder => Path.Combine("Mods", ExplorerCore.NAME);
public string UnhollowedModulesFolder => Path.Combine(
Path.GetDirectoryName(MelonHandler.ModsDirectory),
Path.Combine("MelonLoader", "Managed"));
public ConfigHandler ConfigHandler => _configHandler;
public MelonLoaderConfigHandler _configHandler;
public Action<object> OnLogMessage => MelonLogger.Msg;
public Action<object> OnLogWarning => MelonLogger.Warning;
public Action<object> OnLogError => MelonLogger.Error;
public override void OnApplicationStart()
{
Instance = this;
_configHandler = new MelonLoaderConfigHandler();
ExplorerCore.Init(this);
}
}
}
#endif

View File

@ -0,0 +1,94 @@
#if ML
using MelonLoader;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
namespace UnityExplorer.Loader.ML
{
public class MelonLoaderConfigHandler : ConfigHandler
{
internal const string CTG_NAME = "UnityExplorer";
internal MelonPreferences_Category prefCategory;
public override void Init()
{
prefCategory = MelonPreferences.CreateCategory(CTG_NAME, $"{CTG_NAME} Settings", false, true);
}
public override void LoadConfig()
{
foreach (var entry in ConfigManager.ConfigElements)
{
var key = entry.Key;
if (prefCategory.GetEntry(key) is MelonPreferences_Entry)
{
var config = entry.Value;
config.BoxedValue = config.GetLoaderConfigValue();
}
}
}
// This wrapper exists to handle the arbitrary "LemonAction" delegates which ML now uses in 0.4.4+.
// Reflection is required since the delegate type changed between 0.4.3 and 0.4.4.
// A wrapper class is required to link the MelonPreferences_Entry and the delegate instance.
public class EntryDelegateWrapper<T>
{
public MelonPreferences_Entry<T> entry;
public ConfigElement<T> config;
public EntryDelegateWrapper(MelonPreferences_Entry<T> entry, ConfigElement<T> config)
{
this.entry = entry;
this.config = config;
var evt = entry.GetType().GetEvent("OnValueChangedUntyped");
evt.AddEventHandler(entry, Delegate.CreateDelegate(evt.EventHandlerType, this, GetType().GetMethod("OnChanged")));
}
public void OnChanged()
{
if ((entry.Value == null && config.Value == null) || config.Value.Equals(entry.Value))
return;
config.Value = entry.Value;
}
}
public override void RegisterConfigElement<T>(ConfigElement<T> config)
{
var entry = prefCategory.CreateEntry(config.Name, config.Value, null, config.Description, config.IsInternal, false);
new EntryDelegateWrapper<T>(entry, config);
}
public override void SetConfigValue<T>(ConfigElement<T> config, T value)
{
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
{
entry.Value = value;
//entry.Save();
}
}
public override T GetConfigValue<T>(ConfigElement<T> config)
{
if (prefCategory.GetEntry<T>(config.Name) is MelonPreferences_Entry<T> entry)
return entry.Value;
return default;
}
public override void OnAnyConfigChanged()
{
}
public override void SaveConfig()
{
MelonPreferences.Save();
}
}
}
#endif

View File

@ -18,26 +18,43 @@ namespace UnityExplorer
public class ExplorerStandalone : IExplorerLoader
{
/// <summary>
/// Call this to initialize UnityExplorer without adding a log listener.
/// Call this to initialize UnityExplorer without adding a log listener or Unhollowed modules path.
/// The default Unhollowed path "UnityExplorer\Modules\" will be used.
/// </summary>
/// <returns>The new (or active, if one exists) instance of ExplorerStandalone.</returns>
public static ExplorerStandalone CreateInstance()
=> CreateInstance(null);
public static ExplorerStandalone CreateInstance() => CreateInstance(null, null);
/// <summary>
/// Call this to initialize UnityExplorer and add a listener for UnityExplorer's log messages.
/// Call this to initialize UnityExplorer and add a listener for UnityExplorer's log messages, without specifying an Unhollowed modules path.
/// The default Unhollowed path "UnityExplorer\Modules\" will be used.
/// </summary>
/// <param name="logListener">Your log listener to handle UnityExplorer logs.</param>
/// <returns>The new (or active, if one exists) instance of ExplorerStandalone.</returns>
public static ExplorerStandalone CreateInstance(Action<string, LogType> logListener)
public static ExplorerStandalone CreateInstance(Action<string, LogType> logListener) => CreateInstance(logListener, null);
/// <summary>
/// Call this to initialize UnityExplorer with the provided log listener and Unhollowed modules path.
/// </summary>
/// <param name="logListener">Your log listener to handle UnityExplorer logs.</param>
/// <param name="unhollowedModulesPath">The path of the Unhollowed modules, either relative or absolute.</param>
/// <returns>The new (or active, if one exists) instance of ExplorerStandalone.</returns>
public static ExplorerStandalone CreateInstance(Action<string, LogType> logListener, string unhollowedModulesPath)
{
if (Instance != null)
return Instance;
OnLog += logListener;
var instance = new ExplorerStandalone();
instance.Init();
instance.CheckExplorerFolder();
if (logListener != null)
OnLog += logListener;
if (string.IsNullOrEmpty(unhollowedModulesPath) || !Directory.Exists(unhollowedModulesPath))
instance._unhollowedPath = Path.Combine(instance.ExplorerFolder, "Modules");
else
instance._unhollowedPath = unhollowedModulesPath;
return instance;
}
@ -48,8 +65,8 @@ namespace UnityExplorer
/// </summary>
public static event Action<string, LogType> OnLog;
public Harmony HarmonyInstance => s_harmony;
public static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
public string UnhollowedModulesFolder => _unhollowedPath;
private string _unhollowedPath;
public ConfigHandler ConfigHandler => _configHandler;
private StandaloneConfigHandler _configHandler;
@ -58,19 +75,7 @@ namespace UnityExplorer
{
get
{
if (s_explorerFolder == null)
{
s_explorerFolder =
Path.Combine(
Path.GetDirectoryName(
Uri.UnescapeDataString(new Uri(Assembly.GetExecutingAssembly().CodeBase)
.AbsolutePath)),
"UnityExplorer");
if (!Directory.Exists(s_explorerFolder))
Directory.CreateDirectory(s_explorerFolder);
}
CheckExplorerFolder();
return s_explorerFolder;
}
}
@ -88,45 +93,18 @@ namespace UnityExplorer
ExplorerCore.Init(this);
}
public void SetupCursorPatches()
private void CheckExplorerFolder()
{
try
if (s_explorerFolder == null)
{
this.HarmonyInstance.PatchAll();
}
catch (Exception ex)
{
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
}
}
s_explorerFolder =
Path.Combine(
Path.GetDirectoryName(
Uri.UnescapeDataString(new Uri(Assembly.GetExecutingAssembly().CodeBase).AbsolutePath)),
"UnityExplorer");
[HarmonyPatch(typeof(EventSystem), "current", MethodType.Setter)]
public class PATCH_EventSystem_current
{
[HarmonyPrefix]
public static void Prefix_EventSystem_set_current(ref EventSystem value)
{
CursorUnlocker.Prefix_EventSystem_set_current(ref value);
}
}
[HarmonyPatch(typeof(Cursor), "lockState", MethodType.Setter)]
public class PATCH_Cursor_lockState
{
[HarmonyPrefix]
public static void Prefix_set_lockState(ref CursorLockMode value)
{
CursorUnlocker.Prefix_set_lockState(ref value);
}
}
[HarmonyPatch(typeof(Cursor), "visible", MethodType.Setter)]
public class PATCH_Cursor_visible
{
[HarmonyPrefix]
public static void Prefix_set_visible(ref bool value)
{
CursorUnlocker.Prefix_set_visible(ref value);
if (!Directory.Exists(s_explorerFolder))
Directory.CreateDirectory(s_explorerFolder);
}
}
}

View File

@ -56,13 +56,7 @@ namespace UnityExplorer.ObjectExplorer
else if (m_context == SearchContext.Class)
currentResults = SearchProvider.ClassSearch(nameInputField.Text);
else
{
string compType = "";
if (m_context == SearchContext.UnityObject)
compType = this.desiredTypeInput;
currentResults = SearchProvider.UnityObjectSearch(nameInputField.Text, compType, m_context, m_childFilter, m_sceneFilter);
}
currentResults = SearchProvider.UnityObjectSearch(nameInputField.Text, desiredTypeInput, m_context, m_childFilter, m_sceneFilter);
dataHandler.RefreshData();
resultsScrollPool.Refresh(true);

View File

@ -114,7 +114,7 @@ namespace UnityExplorer.ObjectExplorer
refreshRow.SetActive(!scene.IsValid());
}
private void SceneHandler_OnLoadedScenesChanged(ReadOnlyCollection<Scene> loadedScenes)
private void SceneHandler_OnLoadedScenesChanged(List<Scene> loadedScenes)
{
PopulateSceneDropdown();
}
@ -126,6 +126,9 @@ namespace UnityExplorer.ObjectExplorer
foreach (var scene in SceneHandler.LoadedScenes)
{
if (sceneToDropdownOption.ContainsKey(scene))
continue;
string name = scene.name?.Trim();
if (!scene.IsValid())

View File

@ -29,27 +29,23 @@ namespace UnityExplorer.ObjectExplorer
/// <summary>
/// The GameObjects in the currently inspected scene.
/// </summary>
public static ReadOnlyCollection<GameObject> CurrentRootObjects => new ReadOnlyCollection<GameObject>(rootObjects);
private static GameObject[] rootObjects = new GameObject[0];
public static GameObject[] CurrentRootObjects { get; private set; } = new GameObject[0];
/// <summary>
/// All currently loaded Scenes.
/// </summary>
public static ReadOnlyCollection<Scene> LoadedScenes => new ReadOnlyCollection<Scene>(allLoadedScenes);
private static readonly List<Scene> allLoadedScenes = new List<Scene>();
public static List<Scene> LoadedScenes { get; private set; } = new List<Scene>();
private static HashSet<Scene> previousLoadedScenes;
/// <summary>
/// The names of all scenes in the build settings, if they could be retrieved.
/// </summary>
public static ReadOnlyCollection<string> AllSceneNames => new ReadOnlyCollection<string>(allScenesInBuild);
private static readonly List<string> allScenesInBuild = new List<string>();
public static readonly List<string> AllSceneNames = new List<string>();
/// <summary>
/// Whether or not we successfuly retrieved the names of the scenes in the build settings.
/// </summary>
public static bool WasAbleToGetScenesInBuild => gotAllScenesInBuild;
private static bool gotAllScenesInBuild = true;
public static bool WasAbleToGetScenesInBuild { get; private set; }
/// <summary>
/// Invoked when the currently inspected Scene changes. The argument is the new scene.
@ -59,7 +55,7 @@ namespace UnityExplorer.ObjectExplorer
/// <summary>
/// Invoked whenever the list of currently loaded Scenes changes. The argument contains all loaded scenes after the change.
/// </summary>
public static event Action<ReadOnlyCollection<Scene>> OnLoadedScenesChanged;
public static event Action<List<Scene>> OnLoadedScenesChanged;
/// <summary>
/// Equivalent to <see cref="SceneManager.sceneCount"/> + 2, to include 'DontDestroyOnLoad' and the 'None' scene.
@ -99,12 +95,12 @@ namespace UnityExplorer.ObjectExplorer
for (int i = 0; i < sceneCount; i++)
{
var scenePath = (string)method.Invoke(null, new object[] { i });
allScenesInBuild.Add(scenePath);
AllSceneNames.Add(scenePath);
}
}
catch (Exception ex)
{
gotAllScenesInBuild = false;
WasAbleToGetScenesInBuild = false;
ExplorerCore.LogWarning($"Unable to generate list of all Scenes in the build: {ex}");
}
}
@ -115,7 +111,7 @@ namespace UnityExplorer.ObjectExplorer
int confirmedCount = 2;
bool inspectedExists = SelectedScene == DontDestroyScene || (SelectedScene.HasValue && SelectedScene.Value == default);
allLoadedScenes.Clear();
LoadedScenes.Clear();
for (int i = 0; i < SceneManager.sceneCount; i++)
{
@ -131,30 +127,26 @@ namespace UnityExplorer.ObjectExplorer
if (!inspectedExists && scene == SelectedScene)
inspectedExists = true;
allLoadedScenes.Add(scene);
LoadedScenes.Add(scene);
}
bool anyChange = confirmedCount != allLoadedScenes.Count;
bool anyChange = confirmedCount != LoadedScenes.Count;
allLoadedScenes.Add(DontDestroyScene);
allLoadedScenes.Add(default);
previousLoadedScenes = new HashSet<Scene>(allLoadedScenes);
LoadedScenes.Add(DontDestroyScene);
LoadedScenes.Add(default);
previousLoadedScenes = new HashSet<Scene>(LoadedScenes);
// Default to first scene if none selected or previous selection no longer exists.
if (!inspectedExists)
{
SelectedScene = allLoadedScenes.First();
}
SelectedScene = LoadedScenes.First();
// Notify on the list changing at all
if (anyChange)
{
OnLoadedScenesChanged?.Invoke(LoadedScenes);
}
// Finally, update the root objects list.
if (SelectedScene != null && ((Scene)SelectedScene).IsValid())
rootObjects = RuntimeProvider.Instance.GetRootGameObjects((Scene)SelectedScene);
CurrentRootObjects = RuntimeProvider.Instance.GetRootGameObjects((Scene)SelectedScene);
else
{
var allObjects = RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(GameObject));
@ -165,7 +157,7 @@ namespace UnityExplorer.ObjectExplorer
if (go.transform.parent == null && !go.scene.IsValid())
objects.Add(go);
}
rootObjects = objects.ToArray();
CurrentRootObjects = objects.ToArray();
}
}
}

Binary file not shown.

BIN
src/Resources/legacy.bundle Normal file

Binary file not shown.

BIN
src/Resources/modern.bundle Normal file

Binary file not shown.

View File

@ -209,6 +209,10 @@ namespace UnityExplorer.UI.Panels
{
ExplorerCore.LogWarning("Invalid or corrupt panel save data! Restoring to default.");
SetTransformDefaults();
UIManager.Initializing = false;
DoSaveToConfigElement();
ConfigManager.InternalHandler.SaveConfig();
UIManager.Initializing = true;
}
}
@ -335,7 +339,7 @@ namespace UnityExplorer.UI.Panels
if (!rect)
throw new ArgumentNullException("rect");
return string.Format(ParseUtility.en_US, "{0},{1},{2},{3}", new object[]
return string.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", new object[]
{
rect.anchorMin.x,
rect.anchorMin.y,
@ -349,16 +353,20 @@ namespace UnityExplorer.UI.Panels
if (string.IsNullOrEmpty(stringAnchors))
throw new ArgumentNullException("stringAnchors");
if (stringAnchors.Contains(" "))
// outdated save data, not worth recovering just reset it.
throw new Exception("invalid save data, resetting.");
var split = stringAnchors.Split(',');
if (split.Length != 4)
throw new Exception($"stringAnchors split is unexpected length: {split.Length}");
Vector4 anchors;
anchors.x = float.Parse(split[0], ParseUtility.en_US);
anchors.y = float.Parse(split[1], ParseUtility.en_US);
anchors.z = float.Parse(split[2], ParseUtility.en_US);
anchors.w = float.Parse(split[3], ParseUtility.en_US);
anchors.x = float.Parse(split[0], CultureInfo.InvariantCulture);
anchors.y = float.Parse(split[1], CultureInfo.InvariantCulture);
anchors.z = float.Parse(split[2], CultureInfo.InvariantCulture);
anchors.w = float.Parse(split[3], CultureInfo.InvariantCulture);
panel.anchorMin = new Vector2(anchors.x, anchors.y);
panel.anchorMax = new Vector2(anchors.z, anchors.w);
@ -369,7 +377,7 @@ namespace UnityExplorer.UI.Panels
if (!rect)
throw new ArgumentNullException("rect");
return string.Format(ParseUtility.en_US, "{0},{1}", new object[]
return string.Format(CultureInfo.InvariantCulture, "{0},{1}", new object[]
{
rect.localPosition.x, rect.localPosition.y
});
@ -377,14 +385,21 @@ namespace UnityExplorer.UI.Panels
internal static void SetPositionFromString(this RectTransform rect, string stringPosition)
{
if (string.IsNullOrEmpty(stringPosition))
throw new ArgumentNullException(stringPosition);
if (stringPosition.Contains(" "))
// outdated save data, not worth recovering just reset it.
throw new Exception("invalid save data, resetting.");
var split = stringPosition.Split(',');
if (split.Length != 2)
throw new Exception($"stringPosition split is unexpected length: {split.Length}");
Vector3 vector = rect.localPosition;
vector.x = float.Parse(split[0], ParseUtility.en_US);
vector.y = float.Parse(split[1], ParseUtility.en_US);
vector.x = float.Parse(split[0], CultureInfo.InvariantCulture);
vector.y = float.Parse(split[1], CultureInfo.InvariantCulture);
rect.localPosition = vector;
}
}

View File

@ -16,12 +16,6 @@ namespace UnityExplorer.UI
internal static Vector2 _largeElementSize = new Vector2(100, 30);
internal static Vector2 _smallElementSize = new Vector2(25, 25);
internal static Color _defaultTextColor = Color.white;
internal static Font _defaultFont;
public static void Init()
{
_defaultFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
}
public static GameObject CreateUIObject(string name, GameObject parent, Vector2 size = default)
{
@ -48,7 +42,7 @@ namespace UnityExplorer.UI
internal static void SetDefaultTextValues(Text text)
{
text.color = _defaultTextColor;
text.font = _defaultFont;
text.font = UIManager.DefaultFont;
text.fontSize = 14;
}

View File

@ -36,7 +36,7 @@ namespace UnityExplorer.UI
Bottom
}
public static bool Initializing { get; private set; } = true;
public static bool Initializing { get; internal set; } = true;
private static readonly Dictionary<Panels, UIPanel> UIPanels = new Dictionary<Panels, UIPanel>();
@ -52,6 +52,7 @@ namespace UnityExplorer.UI
internal static GameObject PanelHolder { get; private set; }
internal static Font ConsoleFont { get; private set; }
internal static Font DefaultFont { get; private set; }
internal static Shader BackupShader { get; private set; }
public static RectTransform NavBarRect;
@ -92,8 +93,6 @@ namespace UnityExplorer.UI
{
LoadBundle();
UIFactory.Init();
CreateRootCanvas();
// Global UI Pool Holder
@ -121,6 +120,12 @@ namespace UnityExplorer.UI
lastScreenWidth = Screen.width;
lastScreenHeight = Screen.height;
// Failsafe fix
foreach (var dropdown in CanvasRoot.GetComponentsInChildren<Dropdown>(true))
dropdown.RefreshShownValue();
timeInput.Text = string.Empty;
timeInput.Text = Time.timeScale.ToString();
Initializing = false;
}
@ -404,28 +409,57 @@ namespace UnityExplorer.UI
closeBtn.OnClick += OnCloseButtonClicked;
}
#region UI AssetBundle
// UI AssetBundle
private static void LoadBundle()
{
AssetBundle bundle = null;
AssetBundle bundle;
try
{
bundle = LoadBundle("modern");
if (bundle == null)
bundle = LoadBundle("legacy");
// Get the Major and Minor of the Unity version
var split = Application.unityVersion.Split('.');
int major = int.Parse(split[0]);
int minor = int.Parse(split[1]);
// Use appropriate AssetBundle for Unity version
// >= 2017
if (major >= 2017)
bundle = LoadBundle("modern");
// 5.6.0 to <2017
else if (major == 5 && minor >= 6)
bundle = LoadBundle("legacy.5.6");
// < 5.6.0
else
bundle = LoadBundle("legacy");
}
catch
{
ExplorerCore.LogWarning($"Exception parsing Unity version, falling back to old AssetBundle load method...");
bundle = LoadBundle("modern") ?? LoadBundle("legacy.5.6") ?? LoadBundle("legacy");
}
AssetBundle LoadBundle(string id)
{
ExplorerCore.Log($"Loading {id} bundle for Unity {Application.unityVersion}");
return AssetBundle.LoadFromMemory(ReadFully(typeof(ExplorerCore)
.Assembly
.GetManifestResourceStream($"UnityExplorer.Resources.{id}.bundle")));
}
catch { }
if (bundle == null)
{
ExplorerCore.LogWarning("Could not load the ExplorerUI Bundle!");
ConsoleFont = Resources.GetBuiltinResource<Font>("Arial.ttf");
DefaultFont = ConsoleFont;
return;
}
BackupShader = bundle.LoadAsset<Shader>("DefaultUI");
// Bundle loaded
ConsoleFont = bundle.LoadAsset<Font>("CONSOLA");
DefaultFont = bundle.LoadAsset<Font>("arial");
BackupShader = bundle.LoadAsset<Shader>("DefaultUI");
// Fix for games which don't ship with 'UI/Default' shader.
if (Graphic.defaultGraphicMaterial.shader?.name != "UI/Default")
{
@ -435,15 +469,6 @@ namespace UnityExplorer.UI
else
BackupShader = Graphic.defaultGraphicMaterial.shader;
ConsoleFont = bundle.LoadAsset<Font>("CONSOLA");
}
private static AssetBundle LoadBundle(string id)
{
var stream = typeof(ExplorerCore).Assembly
.GetManifestResourceStream($"UnityExplorer.Resources.explorerui.{id}.bundle");
return AssetBundle.LoadFromMemory(ReadFully(stream));
}
private static byte[] ReadFully(Stream input)
@ -457,7 +482,5 @@ namespace UnityExplorer.UI
return ms.ToArray();
}
}
#endregion
}
}

View File

@ -93,24 +93,6 @@
<IsCpp>true</IsCpp>
<IsStandalone>true</IsStandalone>
</PropertyGroup>
<!-- ML 0.3.0 CPP -->
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release_MLLegacy_Cpp|AnyCPU'">
<OutputPath>..\Release\UnityExplorer.MelonLoader_Legacy.Il2Cpp\</OutputPath>
<DefineConstants>CPP,ML,ML_LEGACY</DefineConstants>
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<AssemblyName>UnityExplorer.MLLEGACY.IL2CPP</AssemblyName>
<IsCpp>true</IsCpp>
<IsMelonLoaderLegacy>true</IsMelonLoaderLegacy>
</PropertyGroup>
<!-- ML 0.3.0 Mono -->
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release_MLLegacy_Mono|AnyCPU'">
<OutputPath>..\Release\UnityExplorer.MelonLoader_Legacy.Mono\</OutputPath>
<DefineConstants>MONO,ML,ML_LEGACY</DefineConstants>
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<AssemblyName>UnityExplorer.MLLEGACY.Mono</AssemblyName>
<IsCpp>false</IsCpp>
<IsMelonLoaderLegacy>true</IsMelonLoaderLegacy>
</PropertyGroup>
<!-- Global refs, Mono and Il2Cpp -->
<ItemGroup>
<Reference Include="System" />
@ -135,17 +117,10 @@
<Private>False</Private>
</Reference>
</ItemGroup>
<!-- MelonLoader Legacy refs -->
<ItemGroup Condition="'$(IsMelonLoaderLegacy)'=='true'">
<Reference Include="MelonLoader">
<HintPath>..\lib\MelonLoader_Legacy\MelonLoader.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<!-- BepInEx universal refs -->
<ItemGroup Condition="'$(IsBepInEx)'=='true'">
<Reference Include="0Harmony">
<HintPath>packages\HarmonyX.2.4.2\lib\net35\0Harmony.dll</HintPath>
<HintPath>packages\HarmonyX.2.5.2\lib\net35\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
@ -181,7 +156,7 @@
<!-- Standalone refs -->
<ItemGroup Condition="'$(IsStandalone)'=='true'">
<Reference Include="0Harmony">
<HintPath>packages\HarmonyX.2.4.2\lib\net35\0Harmony.dll</HintPath>
<HintPath>packages\HarmonyX.2.5.2\lib\net35\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
@ -243,6 +218,7 @@
<Compile Include="Core\Config\InternalConfigHandler.cs" />
<Compile Include="CacheObject\CacheConfigEntry.cs" />
<Compile Include="CacheObject\Views\CacheConfigCell.cs" />
<Compile Include="Core\Reflection\Patches.cs" />
<Compile Include="CSConsole\CSAutoCompleter.cs" />
<Compile Include="CSConsole\LexerBuilder.cs" />
<Compile Include="CSConsole\Lexers\CommentLexer.cs" />
@ -323,13 +299,13 @@
<Compile Include="Core\Tests\TestClass.cs" />
<Compile Include="Core\Utility\UnityHelpers.cs" />
<Compile Include="ExplorerCore.cs" />
<Compile Include="Loader\BIE\BepInExConfigHandler.cs" />
<Compile Include="Loader\BIE\ExplorerBepInPlugin.cs" />
<Compile Include="Loader\BepInEx\BepInExConfigHandler.cs" />
<Compile Include="Loader\BepInEx\ExplorerBepInPlugin.cs" />
<Compile Include="Loader\IExplorerLoader.cs" />
<Compile Include="Loader\ML\ExplorerMelonMod.cs" />
<Compile Include="Loader\ML\MelonLoaderConfigHandler.cs" />
<Compile Include="Loader\STANDALONE\ExplorerStandalone.cs" />
<Compile Include="Loader\STANDALONE\StandaloneConfigHandler.cs" />
<Compile Include="Loader\MelonLoader\ExplorerMelonMod.cs" />
<Compile Include="Loader\MelonLoader\MelonLoaderConfigHandler.cs" />
<Compile Include="Loader\Standalone\ExplorerStandalone.cs" />
<Compile Include="Loader\Standalone\StandaloneConfigHandler.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UI\Models\UIBehaviourModel.cs" />
<Compile Include="UI\Models\UIModel.cs" />

View File

@ -16,8 +16,6 @@ Global
Release_BIE6_Mono|Any CPU = Release_BIE6_Mono|Any CPU
Release_ML_Cpp|Any CPU = Release_ML_Cpp|Any CPU
Release_ML_Mono|Any CPU = Release_ML_Mono|Any CPU
Release_MLLegacy_Cpp|Any CPU = Release_MLLegacy_Cpp|Any CPU
Release_MLLegacy_Mono|Any CPU = Release_MLLegacy_Mono|Any CPU
Release_STANDALONE_Cpp|Any CPU = Release_STANDALONE_Cpp|Any CPU
Release_STANDALONE_Mono|Any CPU = Release_STANDALONE_Mono|Any CPU
EndGlobalSection
@ -32,10 +30,6 @@ Global
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_ML_Cpp|Any CPU.Build.0 = Release|Any CPU
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_ML_Mono|Any CPU.ActiveCfg = Release|Any CPU
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_ML_Mono|Any CPU.Build.0 = Release|Any CPU
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_MLLegacy_Cpp|Any CPU.ActiveCfg = Release|Any CPU
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_MLLegacy_Cpp|Any CPU.Build.0 = Release|Any CPU
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_MLLegacy_Mono|Any CPU.ActiveCfg = Release|Any CPU
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_MLLegacy_Mono|Any CPU.Build.0 = Release|Any CPU
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_STANDALONE_Cpp|Any CPU.ActiveCfg = Release|Any CPU
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_STANDALONE_Cpp|Any CPU.Build.0 = Release|Any CPU
{7B7E5024-385D-4A46-9196-A6AF8F7FBDD5}.Release_STANDALONE_Mono|Any CPU.ActiveCfg = Release|Any CPU
@ -50,10 +44,6 @@ Global
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_ML_Cpp|Any CPU.Build.0 = Release|Any CPU
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_ML_Mono|Any CPU.ActiveCfg = Release|Any CPU
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_ML_Mono|Any CPU.Build.0 = Release|Any CPU
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_MLLegacy_Cpp|Any CPU.ActiveCfg = Release|Any CPU
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_MLLegacy_Cpp|Any CPU.Build.0 = Release|Any CPU
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_MLLegacy_Mono|Any CPU.ActiveCfg = Release|Any CPU
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_MLLegacy_Mono|Any CPU.Build.0 = Release|Any CPU
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_STANDALONE_Cpp|Any CPU.ActiveCfg = Release|Any CPU
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_STANDALONE_Cpp|Any CPU.Build.0 = Release|Any CPU
{E4989E4C-0875-4528-9031-08E2C0E70103}.Release_STANDALONE_Mono|Any CPU.ActiveCfg = Release|Any CPU
@ -68,10 +58,6 @@ Global
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Cpp|Any CPU.Build.0 = Release_ML_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Mono|Any CPU.ActiveCfg = Release_ML_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Mono|Any CPU.Build.0 = Release_ML_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_MLLegacy_Cpp|Any CPU.ActiveCfg = Release_MLLegacy_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_MLLegacy_Cpp|Any CPU.Build.0 = Release_MLLegacy_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_MLLegacy_Mono|Any CPU.ActiveCfg = Release_MLLegacy_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_MLLegacy_Mono|Any CPU.Build.0 = Release_MLLegacy_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Cpp|Any CPU.ActiveCfg = Release_STANDALONE_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Cpp|Any CPU.Build.0 = Release_STANDALONE_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Mono|Any CPU.ActiveCfg = Release_STANDALONE_Mono|Any CPU

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="HarmonyX" version="2.4.2" targetFramework="net35" />
<package id="HarmonyX" version="2.5.2" targetFramework="net35" />
<package id="ILRepack.Lib.MSBuild.Task" version="2.0.18.2" targetFramework="net35" />
<package id="ini-parser" version="2.5.2" targetFramework="net35" />
<package id="Mono.Cecil" version="0.10.4" targetFramework="net35" />