diff --git a/.gitignore b/.gitignore
index 5891524..e645270 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,6 @@
*.user
*.userosscache
*.sln.docstates
-*/mcs-unity*
# User-specific files (MonoDevelop/Xamarin Studio)
*.userprefs
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..efb103e
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,9 @@
+[submodule "lib/Il2CppAssemblyUnhollower"]
+ path = lib/Il2CppAssemblyUnhollower
+ url = https://github.com/knah/Il2CppAssemblyUnhollower
+[submodule "lib/HarmonyX"]
+ path = lib/HarmonyX
+ url = https://github.com/BepInEx/HarmonyX
+[submodule "lib/mcs-unity"]
+ path = lib/mcs-unity
+ url = https://github.com/sinai-dev/mcs-unity
diff --git a/README.md b/README.md
index 41ed3e7..455a144 100644
--- a/README.md
+++ b/README.md
@@ -16,61 +16,39 @@
| ----------- | ------ | ---- |
| [BepInEx](https://github.com/BepInEx/BepInEx) 6.X | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx6.Mono.zip) |
| [BepInEx](https://github.com/BepInEx/BepInEx) 5.X | ✖️ n/a | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx5.Mono.zip) |
-| [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) 0.3 | ✅ [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) |
+| [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) 0.3.1 | ✅ [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) |
| Standalone | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.Standalone.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.Standalone.Mono.zip) |
+### Known issues
+* UI layouts broken/unusable after changing resolutions: delete the file `data.ini` in the UnityExplorer folder (same place as where you put the DLL). Better fix being worked on.
+* Any `MissingMethodException` or `NotSupportedException`: please report the issue and provide a copy of your mod loader log and/or Unity log.
+* The C# console may unexpectedly produce a GC Mark Overflow crash when calling certain outside methods. Not clear yet what is causing this, but it's being looked into.
+* In IL2CPP, some IEnumerable and IDictionary types may fail enumeration. Waiting for the Unhollower rewrite to address this any further.
+* In IL2CPP, the C# console might not suggest deobfuscated (or obfuscated) names. Being looked into.
+
## How to install
### BepInEx
-1. Install [BepInEx](https://github.com/BepInEx/BepInEx) for your game. For IL2CPP you should use [BepInEx 6 (Bleeding Edge)](https://builds.bepis.io/projects/bepinex_be), for Mono you should use [BepInEx 5](https://github.com/BepInEx/BepInEx/releases) (until Mono support stabilizes in BepInEx 6).
+1. Install [BepInEx](https://github.com/BepInEx/BepInEx) for your game. IL2CPP currently requires a [Bleeding Edge](https://builds.bepis.io/projects/bepinex_be) release.
2. Download the UnityExplorer release for BepInEx IL2CPP or Mono above.
3. Take the `UnityExplorer.BIE.___.dll` file and put it in `[GameFolder]\BepInEx\plugins\`
4. In IL2CPP, you will need to download the [Unity libs](https://github.com/LavaGang/Unity-Runtime-Libraries) for the game's Unity version and put them in the `BepInEx\unity-libs\` folder.
### MelonLoader
-1. Install [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) 0.3+ for your game. Version 0.3 is currently in pre-release, so you must "Enable ALPHA Releases" in your MelonLoader Installer settings to see the option for it.
+1. Install [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) 0.3.1+ for your game. This version can currently be obtained from [here](https://github.com/LavaGang/MelonLoader/actions).
2. Download the UnityExplorer release for MelonLoader IL2CPP or Mono above.
3. Take the `UnityExplorer.ML.___.dll` file and put it in the `[GameFolder]\Mods\` folder.
### Standalone
-The standalone release requires you to also load `0Harmony.dll` (HarmonyX, from the `lib\` folder) to function properly, and the IL2CPP also requires `UnhollowerBaseLib.dll` as well. The Mono release should be fairly easy to use with any loader, but the IL2CPP one may be tricky, I'd recommend just using BepInEx or MelonLoader for IL2CPP.
+The standalone release can be used with any injector or loader of your choice, but it requires you to load the dependencies manually: HarmonyX, and the IL2CPP version also requires that you set up an [Il2CppAssemblyUnhollower runtime](https://github.com/knah/Il2CppAssemblyUnhollower#required-external-setup).
-1. Load the UnityExplorer DLL from your mod or inject it, as well as `0Harmony.dll` and `UnhollowerBaseLib.dll` as required.
-2. Create an instance of Unity Explorer with `UnityExplorer.ExplorerStandalone.CreateInstance();`
-3. Optionally subscribe to the `ExplorerStandalone.OnLog` event to handle logging if you wish.
-
-## Issues and contributions
-
-Both issue reports and PR contributions are welcome in this repository.
-
-### Issue reporting
-
-To report an issue with UnityExplorer, please use the following template (as well as any other information / images you can provide).
-
-Template:
-
-```
-* Game issue occurs on:
-* UnityExplorer version: (eg BIE.IL2CPP v3.3.3, etc)
-
-* Description of issue:
-
-* Unity log link:
-* Mod Loader log link:
-```
-
-Please upload your Unity log file to [Pastebin](https://pastebin.com/) (or equivalent service) and provide a link to the paste.
-
-* The log should be found at `%userprofile%\AppData\LocalLow\[Company]\[Game]\` unless redirected by your Mod Loader.
-* The file will be called either `output_log.txt` or `Player.log`
-
-As well as the Unity log, please upload your Mod Loader's log:
-
-* BepInEx: `BepInEx\LogOutput.log`
-* MelonLoader: `MelonLoader\Latest.log`
+1. Load the required libs - HarmonyX, and Il2CppAssemblyUnhollower if IL2CPP
+2. Load the UnityExplorer DLL
+3. Create an instance of Unity Explorer with `UnityExplorer.ExplorerStandalone.CreateInstance();`
+4. Optionally subscribe to the `ExplorerStandalone.OnLog` event to handle logging if you wish
## Features
@@ -80,44 +58,31 @@ As well as the Unity log, please upload your Mod Loader's log:
-* Scene Explorer: Simple menu to traverse the Transform heirarchy of the scene.
-* GameObject Inspector: Various helpful tools to see and manipulate the GameObject, similar to what you can do in the Editor.
-* Reflection Inspector: Inspect Properties and Fields. Can also set primitive values and evaluate primitive methods.
-* Search: Search for UnityEngine.Objects with various filters, or use the helpers for static Instances and Classes.
-* C# Console: Interactive console for evaluating C# methods on the fly, with some basic helpers.
-* Inspect-under-mouse: Hover over an object with a collider and inspect it by clicking on it. There's also a UI mode to inspect UI objects.
+### Object Explorer
-### C# Console Tips
+* Use the Scene Explorer tab to traverse the active scenes, as well as the DontDestroyOnLoad scene and the HideAndDontSave "scene" (assets and hidden objects).
+* Use the Object Search tab to search for Unity objects (including GameObjects, Components, etc), C# Singletons or Static Classes.
-The C# Console can be used to define temporary classes and methods, or it can be used to evaluate an expression, but you cannot do both at the same time.
+### Inspector
-For example, you could run this code to define a temporary class (it will be visible within the console until you run `Reset();`).
+The inspector is used to see detailed information on GameObjects (GameObject Inspector), C# objects (Reflection Inspector) and C# classes (Static Inspector).
-```csharp
-public class MyClass
-{
- public static void Method()
- {
- UnityExplorer.ExplorerCore.Log("hello");
- }
-}
-```
+For the GameObject Inspector, you can edit any of the input fields in the inspector (excluding readonly fields) and press Enter to apply your changes. You can also do this to the GameObject path as a way to change the GameObject's parent. Press the Escape key to cancel your edits.
-You could then delete or comment out the class and run the following expression to run that method:
+In the Reflection Inspectors, automatic updating is not enabled by default, and you must press Apply for any changes you make to take effect.
-```csharp
-MyClass.Method();
-```
+### C# Console
-However, you cannot define a class and run it both at the same time. You must either define class(es) and run that, or define an expression and run that.
+The C# Console uses the `Mono.CSharp.Evaluator` to define temporary classes or run immediate REPL code.
-You can also make use of the helper methods in the console to simplify some tasks, which you can see listed when the console has nothing entered for input. These methods are **not** accessible within any temporary classes you define, they can only be used in the expression context.
+See the "Help" dropdown in the C# console menu for more detailed information.
-### Logging
+### Mouse-Inspect
-Explorer saves all logs to disk (only keeps the most recent 10 logs). They can be found in a "UnityExplorer" folder in the same place as where you put the DLL file.
+The "Mouse Inspect" dropdown on the main UnityExplorer navbar allows you to inspect objects under the mouse.
-These logs are also visible in the Debug Console part of the UI.
+* World: uses Physics.Raycast to look for Colliders
+* UI: uses GraphicRaycasters to find UI objects
### Settings
@@ -139,10 +104,9 @@ Building the project should be straight-forward, the references are all inside t
## Acknowledgments
-* [ManlyMarco](https://github.com/ManlyMarco) for [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor) \[[license](THIRDPARTY_LICENSES.md#runtimeunityeditor-license)\], snippets from the REPL Console were used for UnityExplorer's C# Console.
+* [ManlyMarco](https://github.com/ManlyMarco) for [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor) \[[license](THIRDPARTY_LICENSES.md#runtimeunityeditor-license)\], the ScriptEvaluator from RUE's REPL console was used as the base for UnityExplorer's C# console.
* [denikson](https://github.com/denikson) (aka Horse) for [mcs-unity](https://github.com/denikson/mcs-unity) \[no license\], used as the `Mono.CSharp` reference for the C# Console.
* [HerpDerpenstine](https://github.com/HerpDerpinstine) for [MelonCoroutines](https://github.com/LavaGang/MelonLoader/blob/6cc958ec23b5e2e8453a73bc2e0d5aa353d4f0d1/MelonLoader.Support.Il2Cpp/MelonCoroutines.cs) \[[license](THIRDPARTY_LICENSES.md#melonloader-license)\], they were included for standalone IL2CPP coroutine support.
-* [InGameCodeEditor](https://assetstore.unity.com/packages/tools/gui/ingame-code-editor-144254) \[[license](THIRDPARTY_LICENSES.md#ingamecodeeditor-license)\] was used as the base for the syntax highlighting for UnityExplorer's C# console (`UnityExplorer.UI.Main.CSConsole.Lexer`).
### Disclaimer
diff --git a/THIRDPARTY_LICENSES.md b/THIRDPARTY_LICENSES.md
index 51f1514..78877a8 100644
--- a/THIRDPARTY_LICENSES.md
+++ b/THIRDPARTY_LICENSES.md
@@ -1,6 +1,5 @@
* [RuntimeUnityEditor License](#runtimeunityeditor-license)
* [MelonLoader License](#melonloader-license)
-* [InGameCodeEditor License](#ingamecodeeditor-license)
## RuntimeUnityEditor License
@@ -873,195 +872,3 @@ Public License instead of this License. But first, please read
See the License for the specific language governing permissions and
limitations under the License.
-## InGameCodeEditor License
-
- Apache License
- Version 2.0, January 2004
- http://www.apache.org/licenses/
-
- TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
- 1. Definitions.
-
- "License" shall mean the terms and conditions for use, reproduction,
- and distribution as defined by Sections 1 through 9 of this document.
-
- "Licensor" shall mean the copyright owner or entity authorized by
- the copyright owner that is granting the License.
-
- "Legal Entity" shall mean the union of the acting entity and all
- other entities that control, are controlled by, or are under common
- control with that entity. For the purposes of this definition,
- "control" means (i) the power, direct or indirect, to cause the
- direction or management of such entity, whether by contract or
- otherwise, or (ii) ownership of fifty percent (50%) or more of the
- outstanding shares, or (iii) beneficial ownership of such entity.
-
- "You" (or "Your") shall mean an individual or Legal Entity
- exercising permissions granted by this License.
-
- "Source" form shall mean the preferred form for making modifications,
- including but not limited to software source code, documentation
- source, and configuration files.
-
- "Object" form shall mean any form resulting from mechanical
- transformation or translation of a Source form, including but
- not limited to compiled object code, generated documentation,
- and conversions to other media types.
-
- "Work" shall mean the work of authorship, whether in Source or
- Object form, made available under the License, as indicated by a
- copyright notice that is included in or attached to the work
- (an example is provided in the Appendix below).
-
- "Derivative Works" shall mean any work, whether in Source or Object
- form, that is based on (or derived from) the Work and for which the
- editorial revisions, annotations, elaborations, or other modifications
- represent, as a whole, an original work of authorship. For the purposes
- of this License, Derivative Works shall not include works that remain
- separable from, or merely link (or bind by name) to the interfaces of,
- the Work and Derivative Works thereof.
-
- "Contribution" shall mean any work of authorship, including
- the original version of the Work and any modifications or additions
- to that Work or Derivative Works thereof, that is intentionally
- submitted to Licensor for inclusion in the Work by the copyright owner
- or by an individual or Legal Entity authorized to submit on behalf of
- the copyright owner. For the purposes of this definition, "submitted"
- means any form of electronic, verbal, or written communication sent
- to the Licensor or its representatives, including but not limited to
- communication on electronic mailing lists, source code control systems,
- and issue tracking systems that are managed by, or on behalf of, the
- Licensor for the purpose of discussing and improving the Work, but
- excluding communication that is conspicuously marked or otherwise
- designated in writing by the copyright owner as "Not a Contribution."
-
- "Contributor" shall mean Licensor and any individual or Legal Entity
- on behalf of whom a Contribution has been received by Licensor and
- subsequently incorporated within the Work.
-
- 2. Grant of Copyright License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- copyright license to reproduce, prepare Derivative Works of,
- publicly display, publicly perform, sublicense, and distribute the
- Work and such Derivative Works in Source or Object form.
-
- 3. Grant of Patent License. Subject to the terms and conditions of
- this License, each Contributor hereby grants to You a perpetual,
- worldwide, non-exclusive, no-charge, royalty-free, irrevocable
- (except as stated in this section) patent license to make, have made,
- use, offer to sell, sell, import, and otherwise transfer the Work,
- where such license applies only to those patent claims licensable
- by such Contributor that are necessarily infringed by their
- Contribution(s) alone or by combination of their Contribution(s)
- with the Work to which such Contribution(s) was submitted. If You
- institute patent litigation against any entity (including a
- cross-claim or counterclaim in a lawsuit) alleging that the Work
- or a Contribution incorporated within the Work constitutes direct
- or contributory patent infringement, then any patent licenses
- granted to You under this License for that Work shall terminate
- as of the date such litigation is filed.
-
- 4. Redistribution. You may reproduce and distribute copies of the
- Work or Derivative Works thereof in any medium, with or without
- modifications, and in Source or Object form, provided that You
- meet the following conditions:
-
- (a) You must give any other recipients of the Work or
- Derivative Works a copy of this License; and
-
- (b) You must cause any modified files to carry prominent notices
- stating that You changed the files; and
-
- (c) You must retain, in the Source form of any Derivative Works
- that You distribute, all copyright, patent, trademark, and
- attribution notices from the Source form of the Work,
- excluding those notices that do not pertain to any part of
- the Derivative Works; and
-
- (d) If the Work includes a "NOTICE" text file as part of its
- distribution, then any Derivative Works that You distribute must
- include a readable copy of the attribution notices contained
- within such NOTICE file, excluding those notices that do not
- pertain to any part of the Derivative Works, in at least one
- of the following places: within a NOTICE text file distributed
- as part of the Derivative Works; within the Source form or
- documentation, if provided along with the Derivative Works; or,
- within a display generated by the Derivative Works, if and
- wherever such third-party notices normally appear. The contents
- of the NOTICE file are for informational purposes only and
- do not modify the License. You may add Your own attribution
- notices within Derivative Works that You distribute, alongside
- or as an addendum to the NOTICE text from the Work, provided
- that such additional attribution notices cannot be construed
- as modifying the License.
-
- You may add Your own copyright statement to Your modifications and
- may provide additional or different license terms and conditions
- for use, reproduction, or distribution of Your modifications, or
- for any such Derivative Works as a whole, provided Your use,
- reproduction, and distribution of the Work otherwise complies with
- the conditions stated in this License.
-
- 5. Submission of Contributions. Unless You explicitly state otherwise,
- any Contribution intentionally submitted for inclusion in the Work
- by You to the Licensor shall be under the terms and conditions of
- this License, without any additional terms or conditions.
- Notwithstanding the above, nothing herein shall supersede or modify
- the terms of any separate license agreement you may have executed
- with Licensor regarding such Contributions.
-
- 6. Trademarks. This License does not grant permission to use the trade
- names, trademarks, service marks, or product names of the Licensor,
- except as required for reasonable and customary use in describing the
- origin of the Work and reproducing the content of the NOTICE file.
-
- 7. Disclaimer of Warranty. Unless required by applicable law or
- agreed to in writing, Licensor provides the Work (and each
- Contributor provides its Contributions) on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
- implied, including, without limitation, any warranties or conditions
- of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
- PARTICULAR PURPOSE. You are solely responsible for determining the
- appropriateness of using or redistributing the Work and assume any
- risks associated with Your exercise of permissions under this License.
-
- 8. Limitation of Liability. In no event and under no legal theory,
- whether in tort (including negligence), contract, or otherwise,
- unless required by applicable law (such as deliberate and grossly
- negligent acts) or agreed to in writing, shall any Contributor be
- liable to You for damages, including any direct, indirect, special,
- incidental, or consequential damages of any character arising as a
- result of this License or out of the use or inability to use the
- Work (including but not limited to damages for loss of goodwill,
- work stoppage, computer failure or malfunction, or any and all
- other commercial damages or losses), even if such Contributor
- has been advised of the possibility of such damages.
-
- 9. Accepting Warranty or Additional Liability. While redistributing
- the Work or Derivative Works thereof, You may choose to offer,
- and charge a fee for, acceptance of support, warranty, indemnity,
- or other liability obligations and/or rights consistent with this
- License. However, in accepting such obligations, You may act only
- on Your own behalf and on Your sole responsibility, not on behalf
- of any other Contributor, and only if You agree to indemnify,
- defend, and hold each Contributor harmless for any liability
- incurred by, or claims asserted against, such Contributor by reason
- of your accepting any such warranty or additional liability.
-
- END OF TERMS AND CONDITIONS
-
- Copyright 2020 - 2021 Lava Gang
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
diff --git a/img/preview.png b/img/preview.png
index 584ab16..6865e79 100644
Binary files a/img/preview.png and b/img/preview.png differ
diff --git a/img/social.png b/img/social.png
index 5162087..6a67228 100644
Binary files a/img/social.png and b/img/social.png differ
diff --git a/lib/0Harmony.dll b/lib/0Harmony.dll
deleted file mode 100644
index eec6135..0000000
Binary files a/lib/0Harmony.dll and /dev/null differ
diff --git a/lib/BepInEx.dll b/lib/BepInEx.5/BepInEx.dll
similarity index 100%
rename from lib/BepInEx.dll
rename to lib/BepInEx.5/BepInEx.dll
diff --git a/lib/BepInEx.6.IL2CPP/BepInEx.Core.dll b/lib/BepInEx.6.IL2CPP/BepInEx.Core.dll
new file mode 100644
index 0000000..c708484
Binary files /dev/null and b/lib/BepInEx.6.IL2CPP/BepInEx.Core.dll differ
diff --git a/lib/BepInEx.6.IL2CPP/BepInEx.IL2CPP.dll b/lib/BepInEx.6.IL2CPP/BepInEx.IL2CPP.dll
new file mode 100644
index 0000000..f087db6
Binary files /dev/null and b/lib/BepInEx.6.IL2CPP/BepInEx.IL2CPP.dll differ
diff --git a/lib/BepInEx.6.Mono/BepInEx.Core.dll b/lib/BepInEx.6.Mono/BepInEx.Core.dll
new file mode 100644
index 0000000..35205cd
Binary files /dev/null and b/lib/BepInEx.6.Mono/BepInEx.Core.dll differ
diff --git a/lib/BepInEx.6.Mono/BepInEx.Unity.dll b/lib/BepInEx.6.Mono/BepInEx.Unity.dll
new file mode 100644
index 0000000..1ac65ac
Binary files /dev/null and b/lib/BepInEx.6.Mono/BepInEx.Unity.dll differ
diff --git a/lib/BepInEx.Core.dll b/lib/BepInEx.Core.dll
deleted file mode 100644
index 5121282..0000000
Binary files a/lib/BepInEx.Core.dll and /dev/null differ
diff --git a/lib/BepInEx.IL2CPP.dll b/lib/BepInEx.IL2CPP.dll
deleted file mode 100644
index b8203de..0000000
Binary files a/lib/BepInEx.IL2CPP.dll and /dev/null differ
diff --git a/lib/BepInEx.Unity.dll b/lib/BepInEx.Unity.dll
deleted file mode 100644
index c26ad65..0000000
Binary files a/lib/BepInEx.Unity.dll and /dev/null differ
diff --git a/lib/HarmonyX b/lib/HarmonyX
new file mode 160000
index 0000000..64462b3
--- /dev/null
+++ b/lib/HarmonyX
@@ -0,0 +1 @@
+Subproject commit 64462b3e31abcbc3839fbfae10b620f2a693de31
diff --git a/lib/INIFileParser.dll b/lib/INIFileParser.dll
deleted file mode 100644
index d19e920..0000000
Binary files a/lib/INIFileParser.dll and /dev/null differ
diff --git a/lib/Il2CppAssemblyUnhollower b/lib/Il2CppAssemblyUnhollower
new file mode 160000
index 0000000..0911fda
--- /dev/null
+++ b/lib/Il2CppAssemblyUnhollower
@@ -0,0 +1 @@
+Subproject commit 0911fdaca645b75727c4cf104003918aa8d8296c
diff --git a/lib/MelonLoader.dll b/lib/MelonLoader.dll
deleted file mode 100644
index 7ea7b22..0000000
Binary files a/lib/MelonLoader.dll and /dev/null differ
diff --git a/lib/MelonLoader/MelonLoader.dll b/lib/MelonLoader/MelonLoader.dll
new file mode 100644
index 0000000..133715e
Binary files /dev/null and b/lib/MelonLoader/MelonLoader.dll differ
diff --git a/lib/UnhollowerBaseLib.dll b/lib/UnhollowerBaseLib.dll
deleted file mode 100644
index ab3e40f..0000000
Binary files a/lib/UnhollowerBaseLib.dll and /dev/null differ
diff --git a/lib/UnityEngine.UI.dll b/lib/UnityEngine.UI.dll
deleted file mode 100644
index 74933d5..0000000
Binary files a/lib/UnityEngine.UI.dll and /dev/null differ
diff --git a/lib/UnityEngine.dll b/lib/UnityEngine.dll
deleted file mode 100644
index 29d73f6..0000000
Binary files a/lib/UnityEngine.dll and /dev/null differ
diff --git a/lib/mcs-unity b/lib/mcs-unity
new file mode 160000
index 0000000..0bc7359
--- /dev/null
+++ b/lib/mcs-unity
@@ -0,0 +1 @@
+Subproject commit 0bc7359dd75049c68920830b980a92c5fa35995b
diff --git a/lib/mcs.dll b/lib/mcs.dll
deleted file mode 100644
index e83df45..0000000
Binary files a/lib/mcs.dll and /dev/null differ
diff --git a/lib/mono/UnityEngine.UI.dll b/lib/mono/UnityEngine.UI.dll
new file mode 100644
index 0000000..838d63b
Binary files /dev/null and b/lib/mono/UnityEngine.UI.dll differ
diff --git a/lib/mono/UnityEngine.dll b/lib/mono/UnityEngine.dll
new file mode 100644
index 0000000..606ce38
Binary files /dev/null and b/lib/mono/UnityEngine.dll differ
diff --git a/src/Core/CSharp/ScriptInteraction.cs b/src/Core/CSharp/ScriptInteraction.cs
deleted file mode 100644
index 0c71220..0000000
--- a/src/Core/CSharp/ScriptInteraction.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using System;
-using Mono.CSharp;
-using System.Collections;
-using UnityEngine;
-using System.Collections.Generic;
-using System.Linq;
-using UnityExplorer.Core.Runtime;
-using UnityExplorer.UI.Main.CSConsole;
-using UnityExplorer.UI.Main.Home;
-using UnityExplorer.UI.Inspectors;
-
-namespace UnityExplorer.Core.CSharp
-{
- public class ScriptInteraction : InteractiveBase
- {
- public static void Log(object message)
- {
- ExplorerCore.Log(message);
- }
-
- public static void StartCoroutine(IEnumerator ienumerator)
- {
- RuntimeProvider.Instance.StartCoroutine(ienumerator);
- }
-
- public static void AddUsing(string directive)
- {
- CSharpConsole.Instance.AddUsing(directive);
- }
-
- public static void GetUsing()
- {
- ExplorerCore.Log(CSharpConsole.Instance.Evaluator.GetUsing());
- }
-
- public static void Reset()
- {
- CSharpConsole.Instance.ResetConsole();
- }
-
- public static object CurrentTarget()
- {
- return InspectorManager.Instance?.m_activeInspector?.Target;
- }
-
- public static object[] AllTargets()
- {
- int count = InspectorManager.Instance?.m_currentInspectors.Count ?? 0;
- object[] ret = new object[count];
- for (int i = 0; i < count; i++)
- {
- ret[i] = InspectorManager.Instance?.m_currentInspectors[i].Target;
- }
- return ret;
- }
-
- public static void Inspect(object obj)
- {
- InspectorManager.Instance.Inspect(obj);
- }
-
- public static void Inspect(Type type)
- {
- InspectorManager.Instance.Inspect(type);
- }
- }
-}
\ No newline at end of file
diff --git a/src/Core/CSharp/Suggestion.cs b/src/Core/CSharp/Suggestion.cs
deleted file mode 100644
index 2bdd0dc..0000000
--- a/src/Core/CSharp/Suggestion.cs
+++ /dev/null
@@ -1,70 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Reflection;
-using UnityEngine;
-using UnityExplorer.Core;
-using UnityExplorer.UI.Main.CSConsole;
-
-namespace UnityExplorer.Core.CSharp
-{
- public struct Suggestion
- {
- public enum Contexts
- {
- Namespace,
- Keyword,
- Other
- }
-
- // ~~~~ Instance ~~~~
-
- public readonly string Prefix;
- public readonly string Addition;
- public readonly Contexts Context;
-
- public string Full => Prefix + Addition;
-
- public Color TextColor => GetTextColor();
-
- public Suggestion(string addition, string prefix, Contexts type)
- {
- Addition = addition;
- Prefix = prefix;
- Context = type;
- }
-
- private Color GetTextColor()
- {
- switch (Context)
- {
- case Contexts.Namespace: return Color.grey;
- case Contexts.Keyword: return keywordColor;
- default: return Color.white;
- }
- }
-
- // ~~~~ Static ~~~~
-
- public static HashSet Namespaces => m_namespaces ?? GetNamespaces();
- private static HashSet m_namespaces;
-
- public static HashSet Keywords => m_keywords ?? (m_keywords = new HashSet(CSLexerHighlighter.validKeywordMatcher.Keywords));
- private static HashSet m_keywords;
-
- private static readonly Color keywordColor = new Color(80f / 255f, 150f / 255f, 215f / 255f);
-
- private static HashSet GetNamespaces()
- {
- HashSet set = new HashSet(
- AppDomain.CurrentDomain.GetAssemblies()
- .SelectMany(GetTypes)
- .Where(x => x.IsPublic && !string.IsNullOrEmpty(x.Namespace))
- .Select(x => x.Namespace));
-
- return m_namespaces = set;
-
- IEnumerable GetTypes(Assembly asm) => asm.TryGetTypes();
- }
- }
-}
diff --git a/src/Core/Config/ConfigElement.cs b/src/Core/Config/ConfigElement.cs
index b3510cc..d92a465 100644
--- a/src/Core/Config/ConfigElement.cs
+++ b/src/Core/Config/ConfigElement.cs
@@ -18,6 +18,10 @@ namespace UnityExplorer.Core.Config
public object DefaultValue { get; }
+ public ConfigHandler Handler => IsInternal
+ ? ConfigManager.InternalHandler
+ : ConfigManager.Handler;
+
public T Value
{
get => m_value;
@@ -51,19 +55,19 @@ namespace UnityExplorer.Core.Config
m_value = value;
- ConfigManager.Handler.SetConfigValue(this, value);
+ Handler.SetConfigValue(this, value);
OnValueChanged?.Invoke(value);
OnValueChangedNotify?.Invoke();
- ConfigManager.Handler.OnAnyConfigChanged();
+ Handler.OnAnyConfigChanged();
}
object IConfigElement.GetLoaderConfigValue() => GetLoaderConfigValue();
public T GetLoaderConfigValue()
{
- return ConfigManager.Handler.GetConfigValue(this);
+ return Handler.GetConfigValue(this);
}
public void RevertToDefaultValue()
diff --git a/src/Core/Config/ConfigManager.cs b/src/Core/Config/ConfigManager.cs
index 8ab401a..8489f7c 100644
--- a/src/Core/Config/ConfigManager.cs
+++ b/src/Core/Config/ConfigManager.cs
@@ -6,8 +6,7 @@ using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;
-using UnityExplorer.UI.Main;
-using UnityExplorer.UI.Main.Home;
+using UnityExplorer.UI;
namespace UnityExplorer.Core.Config
{
@@ -17,75 +16,89 @@ namespace UnityExplorer.Core.Config
// See the UnityExplorer.Loader namespace for the implementations.
public static ConfigHandler Handler { get; private set; }
- public static ConfigElement Main_Menu_Toggle;
+ public static ConfigElement Master_Toggle;
+ public static ConfigElement Main_Navbar_Anchor;
public static ConfigElement Force_Unlock_Mouse;
- public static ConfigElement Aggressive_Force_Unlock;
- public static ConfigElement Default_Tab;
- public static ConfigElement Default_Page_Limit;
+ public static ConfigElement Force_Unlock_Toggle;
+ public static ConfigElement Aggressive_Mouse_Unlock;
public static ConfigElement Default_Output_Path;
public static ConfigElement Log_Unity_Debug;
public static ConfigElement Hide_On_Startup;
public static ConfigElement Startup_Delay_Time;
- public static ConfigElement Last_Window_Anchors;
- public static ConfigElement Last_Window_Position;
- public static ConfigElement Last_DebugConsole_State;
- public static ConfigElement Last_SceneExplorer_State;
+ public static ConfigElement Reflection_Signature_Blacklist;
+
+ // internal configs
+ internal static InternalConfigHandler InternalHandler { get; private set; }
+
+ public static ConfigElement ObjectExplorerData;
+ public static ConfigElement InspectorData;
+ public static ConfigElement CSConsoleData;
+ public static ConfigElement OptionsPanelData;
+ public static ConfigElement ConsoleLogData;
internal static readonly Dictionary ConfigElements = new Dictionary();
+ internal static readonly Dictionary InternalConfigs = new Dictionary();
public static void Init(ConfigHandler configHandler)
{
Handler = configHandler;
Handler.Init();
+ InternalHandler = new InternalConfigHandler();
+ InternalHandler.Init();
+
CreateConfigElements();
Handler.LoadConfig();
+ InternalHandler.LoadConfig();
- SceneExplorer.OnToggleShow += SceneExplorer_OnToggleShow;
- PanelDragger.OnFinishResize += PanelDragger_OnFinishResize;
- PanelDragger.OnFinishDrag += PanelDragger_OnFinishDrag;
- DebugConsole.OnToggleShow += DebugConsole_OnToggleShow;
-
- InitConsoleCallback();
+ //InitConsoleCallback();
}
internal static void RegisterConfigElement(ConfigElement configElement)
{
- Handler.RegisterConfigElement(configElement);
- ConfigElements.Add(configElement.Name, configElement);
+ if (!configElement.IsInternal)
+ {
+ Handler.RegisterConfigElement(configElement);
+ ConfigElements.Add(configElement.Name, configElement);
+ }
+ else
+ {
+ InternalHandler.RegisterConfigElement(configElement);
+ InternalConfigs.Add(configElement.Name, configElement);
+ }
}
private static void CreateConfigElements()
{
- Main_Menu_Toggle = new ConfigElement("Main Menu Toggle",
- "The UnityEngine.KeyCode to toggle the UnityExplorer Menu.",
+ Master_Toggle = new ConfigElement("UnityExplorer Toggle",
+ "The key to enable or disable UnityExplorer's menu and features.",
KeyCode.F7);
+ Main_Navbar_Anchor = new ConfigElement("Main Navbar Anchor",
+ "The vertical anchor of the main UnityExplorer Navbar, in case you want to move it.",
+ UIManager.VerticalAnchor.Top);
+
Hide_On_Startup = new ConfigElement("Hide On Startup",
"Should UnityExplorer be hidden on startup?",
false);
- Default_Tab = new ConfigElement("Default Tab",
- "The default menu page when starting the game.",
- MenuPages.Home);
-
- Log_Unity_Debug = new ConfigElement("Log Unity Debug",
- "Should UnityEngine.Debug.Log messages be printed to UnityExplorer's log?",
- false);
-
Force_Unlock_Mouse = new ConfigElement("Force Unlock Mouse",
"Force the Cursor to be unlocked (visible) when the UnityExplorer menu is open.",
true);
- Aggressive_Force_Unlock = new ConfigElement("Aggressive Mouse Unlock",
+ Force_Unlock_Toggle = new ConfigElement("Force Unlock Toggle Key",
+ "The keybind to toggle the 'Force Unlock Mouse' setting. Only usable when UnityExplorer is open.",
+ KeyCode.None);
+
+ Aggressive_Mouse_Unlock = new ConfigElement("Aggressive Mouse Unlock",
"Use WaitForEndOfFrame to aggressively force the Mouse to be unlocked (requires game restart).",
false);
- Default_Page_Limit = new ConfigElement("Default Page Limit",
- "The default maximum number of elements per 'page' in UnityExplorer.",
- 25);
+ Log_Unity_Debug = new ConfigElement("Log Unity Debug",
+ "Should UnityEngine.Debug.Log messages be printed to UnityExplorer's log?",
+ false);
Default_Output_Path = new ConfigElement("Default Output Path",
"The default output path when exporting things from UnityExplorer.",
@@ -95,156 +108,18 @@ namespace UnityExplorer.Core.Config
"The delay on startup before the UI is created.",
1f);
- // Internal configs
+ Reflection_Signature_Blacklist = new ConfigElement("Reflection Signature Blacklist",
+ "Use this to blacklist certain member signatures if they are known to cause a crash or other issues." +
+ "\r\nSeperate signatures with a semicolon ';'.",
+ "DEFAULT");
- Last_Window_Anchors = new ConfigElement("Last_Window_Anchors",
- "For internal use, the last anchors of the UnityExplorer window.",
- DEFAULT_WINDOW_ANCHORS,
- true);
+ // Internal configs (panel save data)
- Last_Window_Position = new ConfigElement("Last_Window_Position",
- "For internal use, the last position of the UnityExplorer window.",
- DEFAULT_WINDOW_POSITION,
- true);
-
- Last_DebugConsole_State = new ConfigElement("Last_DebugConsole_State",
- "For internal use, the collapsed state of the Debug Console.",
- true,
- true);
-
- Last_SceneExplorer_State = new ConfigElement("Last_SceneExplorer_State",
- "For internal use, the collapsed state of the Scene Explorer.",
- true,
- true);
+ ObjectExplorerData = new ConfigElement("ObjectExplorer", "", "", true);
+ InspectorData = new ConfigElement("Inspector", "", "", true);
+ CSConsoleData = new ConfigElement("CSConsole", "", "", true);
+ OptionsPanelData = new ConfigElement("OptionsPanel", "", "", true);
+ ConsoleLogData = new ConfigElement("ConsoleLog", "", "", true);
}
-
- // Internal config callback listeners
-
- private static void PanelDragger_OnFinishResize(RectTransform rect)
- {
- Last_Window_Anchors.Value = rect.RectAnchorsToString();
- PanelDragger_OnFinishDrag(rect);
- }
-
- private static void PanelDragger_OnFinishDrag(RectTransform rect)
- {
- Last_Window_Position.Value = rect.RectPositionToString();
- }
-
- private static void DebugConsole_OnToggleShow(bool showing)
- {
- Last_DebugConsole_State.Value = showing;
- }
-
- private static void SceneExplorer_OnToggleShow(bool showing)
- {
- Last_SceneExplorer_State.Value = showing;
- }
-
- #region CONSOLE ONEXIT CALLBACK
-
- internal static void InitConsoleCallback()
- {
- handler = new ConsoleEventDelegate(ConsoleEventCallback);
- SetConsoleCtrlHandler(handler, true);
- }
-
- static bool ConsoleEventCallback(int eventType)
- {
- // 2 is Console Quit
- if (eventType == 2)
- Handler.SaveConfig();
-
- return false;
- }
-
- static ConsoleEventDelegate handler;
- private delegate bool ConsoleEventDelegate(int eventType);
-
- [DllImport("kernel32.dll", SetLastError = true)]
- private static extern bool SetConsoleCtrlHandler(ConsoleEventDelegate callback, bool add);
-
- #endregion
-
- #region WINDOW ANCHORS / POSITION HELPERS
-
- // Window Anchors helpers
-
- private const string DEFAULT_WINDOW_ANCHORS = "0.25,0.10,0.78,0.95";
- private const string DEFAULT_WINDOW_POSITION = "0,0";
-
- internal static CultureInfo _enCulture = new CultureInfo("en-US");
-
- internal static string RectAnchorsToString(this RectTransform rect)
- {
- try
- {
- return string.Format(_enCulture, "{0},{1},{2},{3}", new object[]
- {
- rect.anchorMin.x,
- rect.anchorMin.y,
- rect.anchorMax.x,
- rect.anchorMax.y
- });
- }
- catch
- {
- return DEFAULT_WINDOW_ANCHORS;
- }
- }
-
- internal static void SetAnchorsFromString(this RectTransform panel, string stringAnchors)
- {
- Vector4 anchors;
- try
- {
- var split = stringAnchors.Split(',');
-
- if (split.Length != 4)
- throw new Exception();
-
- anchors.x = float.Parse(split[0], _enCulture);
- anchors.y = float.Parse(split[1], _enCulture);
- anchors.z = float.Parse(split[2], _enCulture);
- anchors.w = float.Parse(split[3], _enCulture);
- }
- catch
- {
- anchors = new Vector4(0.25f, 0.1f, 0.78f, 0.95f);
- }
-
- panel.anchorMin = new Vector2(anchors.x, anchors.y);
- panel.anchorMax = new Vector2(anchors.z, anchors.w);
- }
-
- internal static string RectPositionToString(this RectTransform rect)
- {
- return string.Format(_enCulture, "{0},{1}", new object[]
- {
- rect.localPosition.x, rect.localPosition.y
- });
- }
-
- internal static void SetPositionFromString(this RectTransform rect, string stringPosition)
- {
- try
- {
- var split = stringPosition.Split(',');
-
- if (split.Length != 2)
- throw new Exception();
-
- Vector3 vector = rect.localPosition;
- vector.x = float.Parse(split[0], _enCulture);
- vector.y = float.Parse(split[1], _enCulture);
- rect.localPosition = vector;
- }
- catch //(Exception ex)
- {
- //ExplorerCore.LogWarning("Exception setting window position: " + ex);
- }
- }
-
- #endregion
}
}
diff --git a/src/Core/Config/InternalConfigHandler.cs b/src/Core/Config/InternalConfigHandler.cs
new file mode 100644
index 0000000..e3ebdf0
--- /dev/null
+++ b/src/Core/Config/InternalConfigHandler.cs
@@ -0,0 +1,108 @@
+using IniParser.Parser;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+using UnityExplorer.UI;
+
+namespace UnityExplorer.Core.Config
+{
+ public class InternalConfigHandler : ConfigHandler
+ {
+ internal static IniDataParser _parser;
+ internal static string INI_PATH;
+
+ public override void Init()
+ {
+ INI_PATH = Path.Combine(ExplorerCore.Loader.ExplorerFolder, "data.ini");
+ _parser = new IniDataParser();
+ _parser.Configuration.CommentString = "#";
+ }
+
+ public override void LoadConfig()
+ {
+ if (!TryLoadConfig())
+ SaveConfig();
+ }
+
+ public override void RegisterConfigElement(ConfigElement element)
+ {
+ // Not necessary
+ }
+
+ public override void SetConfigValue(ConfigElement element, T value)
+ {
+ // Not necessary
+ }
+
+ public override T GetConfigValue(ConfigElement element)
+ {
+ // Not necessary, just return the value.
+ return element.Value;
+ }
+
+ public override void OnAnyConfigChanged()
+ {
+ SaveConfig();
+ }
+
+ public bool TryLoadConfig()
+ {
+ try
+ {
+ if (!File.Exists(INI_PATH))
+ return false;
+
+ string ini = File.ReadAllText(INI_PATH);
+
+ var data = _parser.Parse(ini);
+
+ foreach (var config in data.Sections["Config"])
+ {
+ if (ConfigManager.InternalConfigs.TryGetValue(config.KeyName, out IConfigElement configElement))
+ configElement.BoxedValue = StringToConfigValue(config.Value, configElement.ElementType);
+ }
+
+ return true;
+ }
+ catch (Exception ex)
+ {
+ ExplorerCore.LogWarning("Error loading internal data: " + ex.ToString());
+ return false;
+ }
+ }
+
+ public override void SaveConfig()
+ {
+ if (UIManager.Initializing)
+ return;
+
+ var data = new IniParser.Model.IniData();
+
+ data.Sections.AddSection("Config");
+ var sec = data.Sections["Config"];
+
+ foreach (var entry in ConfigManager.InternalConfigs)
+ sec.AddKey(entry.Key, entry.Value.BoxedValue.ToString());
+
+ if (!Directory.Exists(ExplorerCore.Loader.ConfigFolder))
+ Directory.CreateDirectory(ExplorerCore.Loader.ConfigFolder);
+
+ File.WriteAllText(INI_PATH, data.ToString());
+ }
+
+ public object StringToConfigValue(string value, Type elementType)
+ {
+ if (elementType.IsEnum)
+ return Enum.Parse(elementType, value);
+ else if (elementType == typeof(bool))
+ return bool.Parse(value);
+ else if (elementType == typeof(int))
+ return int.Parse(value);
+ else
+ return value;
+ }
+ }
+}
diff --git a/src/Core/ExplorerBehaviour.cs b/src/Core/ExplorerBehaviour.cs
new file mode 100644
index 0000000..31e260a
--- /dev/null
+++ b/src/Core/ExplorerBehaviour.cs
@@ -0,0 +1,62 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using UnityEngine;
+#if CPP
+using UnhollowerRuntimeLib;
+#endif
+
+namespace UnityExplorer
+{
+ // Handles all Behaviour update calls for UnityExplorer (Update, FixedUpdate, OnPostRender).
+ // Basically just a wrapper which calls the corresponding methods in ExplorerCore.
+
+ public class ExplorerBehaviour : MonoBehaviour
+ {
+ internal static ExplorerBehaviour Instance { get; private set; }
+
+ internal static void Setup()
+ {
+#if CPP
+ ClassInjector.RegisterTypeInIl2Cpp();
+#endif
+
+ var obj = new GameObject("ExplorerBehaviour");
+ GameObject.DontDestroyOnLoad(obj);
+ obj.hideFlags |= HideFlags.HideAndDontSave;
+ Instance = obj.AddComponent();
+ }
+
+#if CPP
+ public ExplorerBehaviour(IntPtr ptr) : base(ptr) { }
+#endif
+
+ internal void Awake()
+ {
+#if CPP
+ Camera.onPostRender = Camera.onPostRender == null
+ ? new Action(OnPostRender)
+ : Il2CppSystem.Delegate.Combine(Camera.onPostRender, (Camera.CameraCallback)new Action(OnPostRender)).Cast();
+
+#else
+ Camera.onPostRender += OnPostRender;
+#endif
+ }
+
+ internal void Update()
+ {
+ ExplorerCore.Update();
+ }
+
+ internal void FixedUpdate()
+ {
+ ExplorerCore.FixedUpdate();
+ }
+
+ internal static void OnPostRender(Camera camera)
+ {
+ ExplorerCore.OnPostRender();
+ }
+ }
+}
diff --git a/src/Core/Input/CursorUnlocker.cs b/src/Core/Input/CursorUnlocker.cs
index 4a535f0..2e0f558 100644
--- a/src/Core/Input/CursorUnlocker.cs
+++ b/src/Core/Input/CursorUnlocker.cs
@@ -1,6 +1,5 @@
using System;
using UnityEngine;
-using UnityExplorer.Core.Unity;
using UnityEngine.EventSystems;
using UnityExplorer.Core.Input;
using BF = System.Reflection.BindingFlags;
@@ -8,11 +7,7 @@ using UnityExplorer.Core.Config;
using UnityExplorer.Core;
using UnityExplorer.UI;
using System.Collections;
-#if ML
-using Harmony;
-#else
using HarmonyLib;
-#endif
namespace UnityExplorer.Core.Input
{
@@ -50,7 +45,7 @@ namespace UnityExplorer.Core.Input
Unlock = ConfigManager.Force_Unlock_Mouse.Value;
ConfigManager.Force_Unlock_Mouse.OnValueChanged += (bool val) => { Unlock = val; };
- if (ConfigManager.Aggressive_Force_Unlock.Value)
+ if (ConfigManager.Aggressive_Mouse_Unlock.Value)
SetupAggressiveUnlock();
}
@@ -167,7 +162,7 @@ namespace UnityExplorer.Core.Input
m_lastVisibleState = (bool?)CursorType.GetProperty("visible", BF.Public | BF.Static)?.GetValue(null, null)
?? false;
- ExplorerCore.Loader.SetupPatches();
+ ExplorerCore.Loader.SetupCursorPatches();
}
catch (Exception e)
{
diff --git a/src/Core/Input/IHandleInput.cs b/src/Core/Input/IHandleInput.cs
index 6fe0bfe..5326f1e 100644
--- a/src/Core/Input/IHandleInput.cs
+++ b/src/Core/Input/IHandleInput.cs
@@ -6,6 +6,7 @@ namespace UnityExplorer.Core.Input
public interface IHandleInput
{
Vector2 MousePosition { get; }
+ Vector2 MouseScrollDelta { get; }
bool GetKeyDown(KeyCode key);
bool GetKey(KeyCode key);
diff --git a/src/Core/Input/InputManager.cs b/src/Core/Input/InputManager.cs
index e905312..624def9 100644
--- a/src/Core/Input/InputManager.cs
+++ b/src/Core/Input/InputManager.cs
@@ -20,14 +20,27 @@ namespace UnityExplorer.Core.Input
public static Vector3 MousePosition => m_inputModule.MousePosition;
- public static bool GetKeyDown(KeyCode key) => m_inputModule.GetKeyDown(key);
- public static bool GetKey(KeyCode key) => m_inputModule.GetKey(key);
+ public static bool GetKeyDown(KeyCode key)
+ {
+ if (key == KeyCode.None)
+ return false;
+ return m_inputModule.GetKeyDown(key);
+ }
+
+ public static bool GetKey(KeyCode key)
+ {
+ if (key == KeyCode.None)
+ return false;
+ return m_inputModule.GetKey(key);
+ }
public static bool GetMouseButtonDown(int btn) => m_inputModule.GetMouseButtonDown(btn);
public static bool GetMouseButton(int btn) => m_inputModule.GetMouseButton(btn);
public static BaseInputModule UIInput => m_inputModule.UIModule;
+ public static Vector2 MouseScrollDelta => m_inputModule.MouseScrollDelta;
+
public static void ActivateUIModule() => m_inputModule.ActivateModule();
public static void AddUIModule()
@@ -38,25 +51,53 @@ namespace UnityExplorer.Core.Input
public static void Init()
{
- if (InputSystem.TKeyboard != null || (ReflectionUtility.LoadModule("Unity.InputSystem") && InputSystem.TKeyboard != null))
- {
- m_inputModule = new InputSystem();
- CurrentType = InputType.InputSystem;
- }
- else if (LegacyInput.TInput != null || (ReflectionUtility.LoadModule("UnityEngine.InputLegacyModule") && LegacyInput.TInput != null))
- {
- m_inputModule = new LegacyInput();
- CurrentType = InputType.Legacy;
- }
-
- if (m_inputModule == null)
- {
- ExplorerCore.LogWarning("Could not find any Input Module Type!");
- m_inputModule = new NoInput();
- CurrentType = InputType.None;
- }
+ InitHandler();
CursorUnlocker.Init();
}
+
+ private static void InitHandler()
+ {
+ // First, just try to use the legacy input, see if its working.
+ // The InputSystem package may be present but not actually activated, so we can find out this way.
+
+ if (LegacyInput.TInput != null || (ReflectionUtility.LoadModule("UnityEngine.InputLegacyModule") && LegacyInput.TInput != null))
+ {
+ try
+ {
+ m_inputModule = new LegacyInput();
+ CurrentType = InputType.Legacy;
+
+ // make sure its working
+ GetKeyDown(KeyCode.F5);
+
+ ExplorerCore.Log("Initialized Legacy Input support");
+ return;
+ }
+ catch
+ {
+ // It's not working, we'll fall back to InputSystem.
+ }
+ }
+
+ if (InputSystem.TKeyboard != null || (ReflectionUtility.LoadModule("Unity.InputSystem") && InputSystem.TKeyboard != null))
+ {
+ try
+ {
+ m_inputModule = new InputSystem();
+ CurrentType = InputType.InputSystem;
+ ExplorerCore.Log("Initialized new InputSystem support.");
+ return;
+ }
+ catch (Exception ex)
+ {
+ ExplorerCore.Log(ex);
+ }
+ }
+
+ ExplorerCore.LogWarning("Could not find any Input Module Type!");
+ m_inputModule = new NoInput();
+ CurrentType = InputType.None;
+ }
}
}
\ No newline at end of file
diff --git a/src/Core/Input/InputSystem.cs b/src/Core/Input/InputSystem.cs
index 5fcf2d5..aa9e99f 100644
--- a/src/Core/Input/InputSystem.cs
+++ b/src/Core/Input/InputSystem.cs
@@ -1,11 +1,9 @@
using System;
using System.Reflection;
-using UnityExplorer.Core.Unity;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer.UI;
using System.Collections.Generic;
-using UnityExplorer.UI.Inspectors;
using System.Linq;
namespace UnityExplorer.Core.Input
@@ -14,8 +12,6 @@ namespace UnityExplorer.Core.Input
{
public InputSystem()
{
- ExplorerCore.Log("Initializing new InputSystem support...");
-
m_kbCurrentProp = TKeyboard.GetProperty("current");
m_kbIndexer = TKeyboard.GetProperty("Item", new Type[] { TKey });
@@ -26,15 +22,18 @@ namespace UnityExplorer.Core.Input
m_mouseCurrentProp = TMouse.GetProperty("current");
m_leftButtonProp = TMouse.GetProperty("leftButton");
m_rightButtonProp = TMouse.GetProperty("rightButton");
+ m_scrollDeltaProp = TMouse.GetProperty("scroll");
m_positionProp = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Pointer")
.GetProperty("position");
- m_readVector2InputMethod = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputControl`1")
+ ReadV2ControlMethod = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputControl`1")
.MakeGenericType(typeof(Vector2))
.GetMethod("ReadValue");
}
+ #region reflection cache
+
public static Type TKeyboard => m_tKeyboard ?? (m_tKeyboard = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Keyboard"));
private static Type m_tKeyboard;
@@ -64,10 +63,17 @@ namespace UnityExplorer.Core.Input
private static object m_rmb;
private static PropertyInfo m_rightButtonProp;
+ private static MethodInfo ReadV2ControlMethod;
+
private static object MousePositionInfo => m_pos ?? (m_pos = m_positionProp.GetValue(CurrentMouse, null));
private static object m_pos;
private static PropertyInfo m_positionProp;
- private static MethodInfo m_readVector2InputMethod;
+
+ private static object MouseScrollInfo => m_scrollInfo ?? (m_scrollInfo = m_scrollDeltaProp.GetValue(CurrentMouse, null));
+ private static object m_scrollInfo;
+ private static PropertyInfo m_scrollDeltaProp;
+
+ #endregion
public Vector2 MousePosition
{
@@ -75,12 +81,21 @@ namespace UnityExplorer.Core.Input
{
try
{
- return (Vector2)m_readVector2InputMethod.Invoke(MousePositionInfo, new object[0]);
+ return (Vector2)ReadV2ControlMethod.Invoke(MousePositionInfo, ArgumentUtility.EmptyArgs);
}
- catch
+ catch { return Vector2.zero; }
+ }
+ }
+
+ public Vector2 MouseScrollDelta
+ {
+ get
+ {
+ try
{
- return Vector2.zero;
+ return (Vector2)ReadV2ControlMethod.Invoke(MouseScrollInfo, ArgumentUtility.EmptyArgs);
}
+ catch { return Vector2.zero; }
}
}
@@ -164,13 +179,13 @@ namespace UnityExplorer.Core.Input
var assetType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionAsset");
m_newInputModule = RuntimeProvider.Instance.AddComponent(UIManager.CanvasRoot, TInputSystemUIInputModule);
var asset = RuntimeProvider.Instance.CreateScriptable(assetType)
- .Cast(assetType);
+ .TryCast(assetType);
inputExtensions = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionSetupExtensions");
var addMap = inputExtensions.GetMethod("AddActionMap", new Type[] { assetType, typeof(string) });
var map = addMap.Invoke(null, new object[] { asset, "UI" })
- .Cast(ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionMap"));
+ .TryCast(ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionMap"));
CreateAction(map, "point", new[] { "/position" }, "point");
CreateAction(map, "click", new[] { "/leftButton" }, "leftClick");
@@ -178,7 +193,7 @@ namespace UnityExplorer.Core.Input
CreateAction(map, "scrollWheel", new[] { "/scroll" }, "scrollWheel");
UI_Enable = map.GetType().GetMethod("Enable");
- UI_Enable.Invoke(map, new object[0]);
+ UI_Enable.Invoke(map, ArgumentUtility.EmptyArgs);
UI_ActionMap = map;
}
@@ -191,28 +206,28 @@ namespace UnityExplorer.Core.Input
var inputActionType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputAction");
var addAction = inputExtensions.GetMethod("AddAction");
var action = addAction.Invoke(null, new object[] { map, actionName, default, null, null, null, null, null })
- .Cast(inputActionType);
+ .TryCast(inputActionType);
var addBinding = inputExtensions.GetMethod("AddBinding",
new Type[] { inputActionType, typeof(string), typeof(string), typeof(string), typeof(string) });
foreach (string binding in bindings)
- addBinding.Invoke(null, new object[] { action.Cast(inputActionType), binding, null, null, null });
+ addBinding.Invoke(null, new object[] { action.TryCast(inputActionType), binding, null, null, null });
var refType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionReference");
var inputRef = refType.GetMethod("Create")
.Invoke(null, new object[] { action })
- .Cast(refType);
+ .TryCast(refType);
TInputSystemUIInputModule
.GetProperty(propertyName)
- .SetValue(m_newInputModule.Cast(TInputSystemUIInputModule), inputRef, null);
+ .SetValue(m_newInputModule.TryCast(TInputSystemUIInputModule), inputRef, null);
}
public void ActivateModule()
{
m_newInputModule.ActivateModule();
- UI_Enable.Invoke(UI_ActionMap, new object[0]);
+ UI_Enable.Invoke(UI_ActionMap, ArgumentUtility.EmptyArgs);
}
}
}
\ No newline at end of file
diff --git a/src/Core/Input/LegacyInput.cs b/src/Core/Input/LegacyInput.cs
index 65d4ad1..c662fa2 100644
--- a/src/Core/Input/LegacyInput.cs
+++ b/src/Core/Input/LegacyInput.cs
@@ -1,6 +1,5 @@
using System;
using System.Reflection;
-using UnityExplorer.Core.Unity;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer.UI;
@@ -11,9 +10,8 @@ namespace UnityExplorer.Core.Input
{
public LegacyInput()
{
- ExplorerCore.Log("Initializing Legacy Input support...");
-
m_mousePositionProp = TInput.GetProperty("mousePosition");
+ m_mouseDeltaProp = TInput.GetProperty("mouseScrollDelta");
m_getKeyMethod = TInput.GetMethod("GetKey", new Type[] { typeof(KeyCode) });
m_getKeyDownMethod = TInput.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) });
m_getMouseButtonMethod = TInput.GetMethod("GetMouseButton", new Type[] { typeof(int) });
@@ -24,6 +22,7 @@ namespace UnityExplorer.Core.Input
private static Type m_tInput;
private static PropertyInfo m_mousePositionProp;
+ private static PropertyInfo m_mouseDeltaProp;
private static MethodInfo m_getKeyMethod;
private static MethodInfo m_getKeyDownMethod;
private static MethodInfo m_getMouseButtonMethod;
@@ -31,6 +30,8 @@ namespace UnityExplorer.Core.Input
public Vector2 MousePosition => (Vector3)m_mousePositionProp.GetValue(null, null);
+ public Vector2 MouseScrollDelta => (Vector2)m_mouseDeltaProp.GetValue(null, null);
+
public bool GetKey(KeyCode key) => (bool)m_getKeyMethod.Invoke(null, new object[] { key });
public bool GetKeyDown(KeyCode key) => (bool)m_getKeyDownMethod.Invoke(null, new object[] { key });
diff --git a/src/Core/Input/NoInput.cs b/src/Core/Input/NoInput.cs
index 159767e..f56140f 100644
--- a/src/Core/Input/NoInput.cs
+++ b/src/Core/Input/NoInput.cs
@@ -8,6 +8,7 @@ namespace UnityExplorer.Core.Input
public class NoInput : IHandleInput
{
public Vector2 MousePosition => Vector2.zero;
+ public Vector2 MouseScrollDelta => Vector2.zero;
public bool GetKey(KeyCode key) => false;
public bool GetKeyDown(KeyCode key) => false;
diff --git a/src/Core/Reflection/Extensions.cs b/src/Core/Reflection/Extensions.cs
new file mode 100644
index 0000000..f948fd5
--- /dev/null
+++ b/src/Core/Reflection/Extensions.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+
+namespace UnityExplorer
+{
+ public static class ReflectionExtensions
+ {
+ // ReflectionUtility extensions
+
+ public static Type GetActualType(this object obj)
+ => ReflectionUtility.Instance.Internal_GetActualType(obj);
+
+ public static object TryCast(this object obj)
+ => ReflectionUtility.Instance.Internal_TryCast(obj, ReflectionUtility.Instance.Internal_GetActualType(obj));
+
+ public static object TryCast(this object obj, Type castTo)
+ => ReflectionUtility.Instance.Internal_TryCast(obj, castTo);
+
+ public static T TryCast(this object obj)
+ {
+ try
+ {
+ return (T)ReflectionUtility.Instance.Internal_TryCast(obj, typeof(T));
+ }
+ catch
+ {
+ return default;
+ }
+ }
+
+ public static HashSet GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric)
+ => ReflectionUtility.GetImplementationsOf(baseType, allowAbstract, allowGeneric);
+
+ // ------- Misc extensions --------
+
+ ///
+ /// Safely try to get all Types inside an Assembly.
+ ///
+ public static IEnumerable TryGetTypes(this Assembly asm)
+ {
+ try
+ {
+ return asm.GetTypes();
+ }
+ catch (ReflectionTypeLoadException e)
+ {
+ try
+ {
+ return asm.GetExportedTypes();
+ }
+ catch
+ {
+ return e.Types.Where(t => t != null);
+ }
+ }
+ catch
+ {
+ return Enumerable.Empty();
+ }
+ }
+
+
+ ///
+ /// Check if the two objects are reference-equal, including checking for UnityEngine.Object-equality and Il2CppSystem.Object-equality.
+ ///
+ public static bool ReferenceEqual(this object objA, object objB)
+ {
+ if (object.ReferenceEquals(objA, objB))
+ return true;
+
+ if (objA is UnityEngine.Object unityA && objB is UnityEngine.Object unityB)
+ {
+ if (unityA && unityB && unityA.m_CachedPtr == unityB.m_CachedPtr)
+ return true;
+ }
+
+#if CPP
+ if (objA is Il2CppSystem.Object cppA && objB is Il2CppSystem.Object cppB
+ && cppA.Pointer == cppB.Pointer)
+ return true;
+#endif
+
+ return false;
+ }
+
+ ///
+ /// Helper to display a simple "{ExceptionType}: {Message}" of the exception, and optionally use the inner-most exception.
+ ///
+ public static string ReflectionExToString(this Exception e, bool innerMost = true)
+ {
+ if (innerMost)
+ e = e.GetInnerMostException();
+
+ return $"{e.GetType()}: {e.Message}";
+ }
+
+ public static Exception GetInnerMostException(this Exception e)
+ {
+ while (e.InnerException != null)
+ {
+#if CPP
+ if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException)
+ break;
+#endif
+ e = e.InnerException;
+ }
+
+ return e;
+ }
+ }
+}
diff --git a/src/Core/Reflection/Il2CppReflection.cs b/src/Core/Reflection/Il2CppReflection.cs
new file mode 100644
index 0000000..d372361
--- /dev/null
+++ b/src/Core/Reflection/Il2CppReflection.cs
@@ -0,0 +1,893 @@
+#if CPP
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using UnhollowerBaseLib;
+using UnhollowerRuntimeLib;
+using System.Runtime.InteropServices;
+using System.Reflection;
+using System.Collections;
+using System.IO;
+using System.Diagnostics.CodeAnalysis;
+using UnityExplorer.Core;
+using CppType = Il2CppSystem.Type;
+using BF = System.Reflection.BindingFlags;
+using UnityExplorer.Core.Config;
+
+namespace UnityExplorer
+{
+ public class Il2CppReflection : ReflectionUtility
+ {
+ protected override void Initialize()
+ {
+ base.Initialize();
+
+ TryLoadGameModules();
+
+ BuildDeobfuscationCache();
+ OnTypeLoaded += TryCacheDeobfuscatedType;
+ }
+
+ #region IL2CPP Extern and pointers
+
+ // Extern C++ methods
+ [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass);
+
+ [DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+ public static extern IntPtr il2cpp_object_get_class(IntPtr obj);
+
+ public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _);
+
+ public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr)
+ {
+ if (cppClassPointers.TryGetValue(type.AssemblyQualifiedName, out il2cppPtr))
+ return il2cppPtr != IntPtr.Zero;
+
+ il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
+ .MakeGenericType(new Type[] { type })
+ .GetField("NativeClassPtr", BF.Public | BF.Static)
+ .GetValue(null);
+
+ cppClassPointers.Add(type.AssemblyQualifiedName, il2cppPtr);
+
+ return il2cppPtr != IntPtr.Zero;
+ }
+
+ #endregion
+
+
+ #region Deobfuscation cache
+
+ private static readonly Dictionary DeobfuscatedTypes = new Dictionary();
+ private static readonly Dictionary reverseDeobCache = new Dictionary();
+
+ private static void BuildDeobfuscationCache()
+ {
+ foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
+ {
+ foreach (var type in asm.TryGetTypes())
+ TryCacheDeobfuscatedType(type);
+ }
+
+ if (DeobfuscatedTypes.Count > 0)
+ ExplorerCore.Log($"Built IL2CPP deobfuscation cache, initial count: {DeobfuscatedTypes.Count}");
+ }
+
+ private static void TryCacheDeobfuscatedType(Type type)
+ {
+ try
+ {
+ // Thanks to Slaynash for this
+ if (type.CustomAttributes.Any(it => it.AttributeType.Name == "ObfuscatedNameAttribute"))
+ {
+ var cppType = Il2CppType.From(type);
+
+ if (!DeobfuscatedTypes.ContainsKey(cppType.FullName))
+ {
+ DeobfuscatedTypes.Add(cppType.FullName, type);
+ reverseDeobCache.Add(type.FullName, cppType.FullName);
+ }
+ }
+ }
+ catch { }
+ }
+
+ internal override string Internal_ProcessTypeInString(string theString, Type type)
+ {
+ if (reverseDeobCache.TryGetValue(type.FullName, out string obName))
+ return theString.Replace(obName, type.FullName);
+
+ return theString;
+ }
+
+ #endregion
+
+
+ // Get type by name
+
+ internal override Type Internal_GetTypeByName(string fullName)
+ {
+ if (DeobfuscatedTypes.TryGetValue(fullName, out Type deob))
+ return deob;
+
+ return base.Internal_GetTypeByName(fullName);
+ }
+
+ #region Get actual type
+
+ internal override Type Internal_GetActualType(object obj)
+ {
+ if (obj == null)
+ return null;
+
+ var type = obj.GetType();
+
+ try
+ {
+ if (IsString(obj))
+ return typeof(string);
+
+ if (IsIl2CppPrimitive(type))
+ return il2cppPrimitivesToMono[type.FullName];
+
+ if (obj is Il2CppSystem.Object cppObject)
+ {
+ var cppType = cppObject.GetIl2CppType();
+
+ // check if type is injected
+ IntPtr classPtr = il2cpp_object_get_class(cppObject.Pointer);
+ if (RuntimeSpecificsStore.IsInjected(classPtr))
+ {
+ // Note: This will fail on injected subclasses.
+ // - {Namespace}.{Class}.{Subclass} would be {Namespace}.{Subclass} when injected.
+ // Not sure on solution yet.
+ return GetTypeByName(cppType.FullName) ?? type;
+ }
+
+ if (AllTypes.TryGetValue(cppType.FullName, out Type primitive) && primitive.IsPrimitive)
+ return primitive;
+
+ return GetUnhollowedType(cppType) ?? type;
+ }
+ }
+ catch (Exception ex)
+ {
+ ExplorerCore.LogWarning("Exception in IL2CPP GetActualType: " + ex);
+ }
+
+ return type;
+ }
+
+ public static Type GetUnhollowedType(CppType cppType)
+ {
+ var fullname = cppType.FullName;
+
+ if (DeobfuscatedTypes.TryGetValue(fullname, out Type deob))
+ return deob;
+
+ if (fullname.StartsWith("System."))
+ fullname = $"Il2Cpp{fullname}";
+
+ AllTypes.TryGetValue(fullname, out Type monoType);
+ return monoType;
+ }
+
+ #endregion
+
+
+ #region Casting
+
+ private static readonly Dictionary cppClassPointers = new Dictionary();
+
+ internal override object Internal_TryCast(object obj, Type castTo)
+ {
+ if (obj == null)
+ return null;
+
+ var type = obj.GetType();
+
+ if (type == castTo)
+ return obj;
+
+ // from structs
+ if (type.IsValueType)
+ {
+ // from il2cpp primitive to system primitive
+ if (IsIl2CppPrimitive(type) && castTo.IsPrimitive)
+ {
+ return MakeMonoPrimitive(obj);
+ }
+ // from system primitive to il2cpp primitive
+ else if (IsIl2CppPrimitive(castTo))
+ {
+ return MakeIl2CppPrimitive(castTo, obj);
+ }
+ // from other structs to il2cpp object
+ else if (typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
+ {
+ return BoxIl2CppObject(obj);
+ }
+ else
+ return obj;
+ }
+
+ // from string to il2cpp.Object / il2cpp.String
+ if (obj is string && typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
+ {
+ return BoxStringToType(obj, castTo);
+ }
+
+ // from il2cpp objects...
+
+ if (!(obj is Il2CppObjectBase cppObj))
+ return obj;
+
+ // from Il2CppSystem.Object to a struct
+ if (castTo.IsValueType)
+ return UnboxCppObject(cppObj, castTo);
+ // or to system string
+ else if (castTo == typeof(string))
+ return UnboxString(obj);
+
+ // Casting from il2cpp object to il2cpp object...
+
+ if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr))
+ return obj;
+
+ IntPtr castFromPtr = il2cpp_object_get_class(cppObj.Pointer);
+
+ if (!il2cpp_class_is_assignable_from(castToPtr, castFromPtr))
+ return null;
+
+ if (RuntimeSpecificsStore.IsInjected(castToPtr))
+ {
+ var injectedObj = UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(cppObj.Pointer);
+ return injectedObj ?? obj;
+ }
+
+ try
+ {
+ return Activator.CreateInstance(castTo, cppObj.Pointer);
+ }
+ catch
+ {
+ return obj;
+ }
+ }
+
+ #endregion
+
+
+ #region Boxing and unboxing ValueTypes
+
+ // cached il2cpp unbox methods
+ internal static readonly Dictionary unboxMethods = new Dictionary();
+
+ // Unbox an il2cpp object to a struct or System primitive.
+ public object UnboxCppObject(Il2CppObjectBase cppObj, Type toType)
+ {
+ if (!toType.IsValueType)
+ return null;
+
+ try
+ {
+ if (toType.IsEnum)
+ return Enum.Parse(toType, cppObj.ToString());
+
+ var name = toType.AssemblyQualifiedName;
+
+ if (!unboxMethods.ContainsKey(name))
+ {
+ unboxMethods.Add(name, typeof(Il2CppObjectBase)
+ .GetMethod("Unbox")
+ .MakeGenericMethod(toType));
+ }
+
+ return unboxMethods[name].Invoke(cppObj, ArgumentUtility.EmptyArgs);
+ }
+ catch (Exception ex)
+ {
+ ExplorerCore.LogWarning("Exception Unboxing Il2Cpp object to struct: " + ex);
+ return null;
+ }
+ }
+
+ private static Il2CppSystem.Object BoxIl2CppObject(object cppStruct, Type structType)
+ {
+ return GetMethodInfo(structType, "BoxIl2CppObject", ArgumentUtility.EmptyTypes)
+ .Invoke(cppStruct, ArgumentUtility.EmptyArgs)
+ as Il2CppSystem.Object;
+ }
+
+ public Il2CppSystem.Object BoxIl2CppObject(object value)
+ {
+ if (value == null)
+ return null;
+
+ try
+ {
+ var type = value.GetType();
+ if (!type.IsValueType)
+ return null;
+
+ if (type.IsEnum)
+ return Il2CppSystem.Enum.Parse(Il2CppType.From(type), value.ToString());
+
+ if (type.IsPrimitive && AllTypes.TryGetValue($"Il2Cpp{type.FullName}", out Type cppType))
+ return BoxIl2CppObject(MakeIl2CppPrimitive(cppType, value), cppType);
+
+ return BoxIl2CppObject(value, type);
+ }
+ catch (Exception ex)
+ {
+ ExplorerCore.LogWarning("Exception in BoxIl2CppObject: " + ex);
+ return null;
+ }
+ }
+
+ // Helpers for Il2Cpp primitive <-> Mono
+
+ internal static readonly Dictionary il2cppPrimitivesToMono = new Dictionary
+ {
+ { "Il2CppSystem.Boolean", typeof(bool) },
+ { "Il2CppSystem.Byte", typeof(byte) },
+ { "Il2CppSystem.SByte", typeof(sbyte) },
+ { "Il2CppSystem.Char", typeof(char) },
+ { "Il2CppSystem.Double", typeof(double) },
+ { "Il2CppSystem.Single", typeof(float) },
+ { "Il2CppSystem.Int32", typeof(int) },
+ { "Il2CppSystem.UInt32", typeof(uint) },
+ { "Il2CppSystem.Int64", typeof(long) },
+ { "Il2CppSystem.UInt64", typeof(ulong) },
+ { "Il2CppSystem.Int16", typeof(short) },
+ { "Il2CppSystem.UInt16", typeof(ushort) },
+ { "Il2CppSystem.IntPtr", typeof(IntPtr) },
+ { "Il2CppSystem.UIntPtr", typeof(UIntPtr) }
+ };
+
+ public static bool IsIl2CppPrimitive(object obj) => IsIl2CppPrimitive(obj.GetType());
+
+ public static bool IsIl2CppPrimitive(Type type) => il2cppPrimitivesToMono.ContainsKey(type.FullName);
+
+ public object MakeMonoPrimitive(object cppPrimitive)
+ {
+ return GetFieldInfo(cppPrimitive.GetType(), "m_value").GetValue(cppPrimitive);
+ }
+
+ public object MakeIl2CppPrimitive(Type cppType, object monoValue)
+ {
+ var cppStruct = Activator.CreateInstance(cppType);
+ GetFieldInfo(cppType, "m_value").SetValue(cppStruct, monoValue);
+ return cppStruct;
+ }
+
+ #endregion
+
+
+ #region String boxing/unboxing
+
+ private const string IL2CPP_STRING_FULLNAME = "Il2CppSystem.String";
+ private const string STRING_FULLNAME = "System.String";
+
+ public bool IsString(object obj)
+ {
+ if (obj is string || obj is Il2CppSystem.String)
+ return true;
+
+ if (obj is Il2CppSystem.Object cppObj)
+ {
+ var type = cppObj.GetIl2CppType();
+ return type.FullName == IL2CPP_STRING_FULLNAME || type.FullName == STRING_FULLNAME;
+ }
+
+ return false;
+ }
+
+ public object BoxStringToType(object value, Type castTo)
+ {
+ if (castTo == typeof(Il2CppSystem.String))
+ return (Il2CppSystem.String)(value as string);
+ else
+ return (Il2CppSystem.Object)(value as string);
+ }
+
+ public string UnboxString(object value)
+ {
+ if (value is string s)
+ return s;
+
+ s = null;
+ if (value is Il2CppSystem.Object cppObject)
+ s = cppObject.ToString();
+ else if (value is Il2CppSystem.String cppString)
+ s = cppString;
+
+ return s;
+ }
+
+ #endregion
+
+
+ #region Singleton finder
+
+ internal override void Internal_FindSingleton(string[] possibleNames, Type type, BF flags, List