mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-22 08:32:51 +08:00
Compare commits
165 Commits
Author | SHA1 | Date | |
---|---|---|---|
798b881dcb | |||
c90f339882 | |||
c351ce5a6d | |||
9985ed0fd8 | |||
f35232bb64 | |||
49cc1f7df3 | |||
9f78c50854 | |||
bc64a04339 | |||
7d961ce8ad | |||
c7d7569f71 | |||
ee8f33a754 | |||
a95d91c824 | |||
177b78f4e7 | |||
3804d0aab0 | |||
97a82c1661 | |||
96a1cc69fb | |||
589abe6193 | |||
91bb58b020 | |||
d67507ead2 | |||
d730fbe49c | |||
9e0784ce54 | |||
67c602b32f | |||
8fb7d87ca6 | |||
c740c3c54d | |||
ea7b91f7fd | |||
c79223f537 | |||
aae248bf69 | |||
a73e992a0c | |||
3984550bb6 | |||
7928363aeb | |||
12758606fa | |||
c927ac54be | |||
9f1df11940 | |||
62354b6aa2 | |||
44e57c32c3 | |||
274352d665 | |||
7b477a8b0e | |||
e585fc6da0 | |||
0dd19345ed | |||
0050cff185 | |||
5f1d635eb3 | |||
e0f7fad00e | |||
f92c215a88 | |||
d58a5df82c | |||
12dd85ecfd | |||
03fb165967 | |||
834f4b7c4c | |||
3415eb8e77 | |||
6c1a5b8ae2 | |||
3334549902 | |||
077a2b434a | |||
8123ec2412 | |||
3f31db8dd8 | |||
9c1d459655 | |||
2cb510a82e | |||
6aa9b3aa15 | |||
587842f46b | |||
b9c641f170 | |||
1e68529430 | |||
e84e96a63c | |||
ec8c88ab9b | |||
0eae78eeb2 | |||
a07ead2142 | |||
b93567c7ea | |||
957d80c7ec | |||
d530d10798 | |||
66daabf770 | |||
9fe998aa22 | |||
28b6db80f9 | |||
22effbb399 | |||
50f0c31e98 | |||
911522457e | |||
d5d1841109 | |||
239534e09c | |||
bc5d16051f | |||
f81822f219 | |||
f159cf5ea7 | |||
1e6bacb32b | |||
a80cef4c1d | |||
450bb77f2e | |||
0479102db6 | |||
93181f02be | |||
371054d6df | |||
427f23b80a | |||
a0d5ab8792 | |||
297034e38b | |||
57f59d1295 | |||
fbdb84eefa | |||
6989ea1b19 | |||
fcdfeb2dec | |||
a1d0b6432e | |||
0b84405e57 | |||
c31e0949d3 | |||
5f0495a7ea | |||
28de6779d8 | |||
fae85c2968 | |||
fc8fa9aa7a | |||
bcf9a801a9 | |||
20298aa47b | |||
e2c1c186c3 | |||
9ce6508828 | |||
506e75c5fe | |||
a2f22051f0 | |||
4e3203a91b | |||
a411ce2dba | |||
48132b3d46 | |||
e49ed3028f | |||
31dd54d25c | |||
3cfdd6fa43 | |||
1fa1283a68 | |||
afa4135b67 | |||
48d1cf574d | |||
df0abbc847 | |||
5c9dcb1d43 | |||
602770d980 | |||
f26371f95f | |||
a673c39f4a | |||
40583cae3d | |||
20018a9ba9 | |||
dabf92a1a5 | |||
b7e275f02c | |||
f393e0d706 | |||
ab8acc9e84 | |||
66c30ee70e | |||
ddd271c00d | |||
30fe9e4dde | |||
fa3a436037 | |||
ca27d2b20f | |||
4315e0c547 | |||
3e19e74329 | |||
5a3ffebadc | |||
6e6b6239d8 | |||
03c8e5a8bd | |||
44f7209843 | |||
734e45cf9f | |||
97838e0b3a | |||
304b47f898 | |||
3d1fcbcd9f | |||
36fc17aa43 | |||
adfa29e63c | |||
3ffdcea73b | |||
dfd55260a8 | |||
29c78dc5a6 | |||
bf59d9d6cd | |||
bb0c59534a | |||
802bb722bc | |||
9ca992b0d7 | |||
5f3b3a6870 | |||
f5bce439cb | |||
e2b2c9038a | |||
fbefccd6b7 | |||
271c91f0d0 | |||
eb221bd868 | |||
5b516eb4cc | |||
601567f9d2 | |||
7ff508b874 | |||
09a7cd35cf | |||
db4a338d26 | |||
c08e02057c | |||
362fcdc51a | |||
73bd172e4d | |||
454d3bd0b4 | |||
f815a13d9a | |||
65c4d49274 | |||
d99137526e |
3
.github/FUNDING.yml
vendored
Normal file
3
.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
ko_fi: sinaidev
|
60
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
60
.github/ISSUE_TEMPLATE/bug_report.yaml
vendored
Normal file
@ -0,0 +1,60 @@
|
||||
name: Bug Report
|
||||
description: File a bug or crash report
|
||||
title: "[Bug]: "
|
||||
labels: [bug]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for submitting a bug report, please fill out as much detail as possible.
|
||||
- type: checkboxes
|
||||
id: latestversion
|
||||
attributes:
|
||||
label: Are you on the latest version of UnityExplorer?
|
||||
description: If not, you must update first.
|
||||
options:
|
||||
- label: Yes, I'm on the latest version of UnityExplorer.
|
||||
required: true
|
||||
- type: dropdown
|
||||
id: version
|
||||
attributes:
|
||||
label: Which release are you using?
|
||||
description: Please select your environment for UnityExplorer.
|
||||
options:
|
||||
- BepInEx IL2CPP
|
||||
- BepInEx 6.X Mono
|
||||
- BepInEx 5.X Mono
|
||||
- MelonLoader IL2CPP
|
||||
- MelonLoader Mono
|
||||
- Standalone IL2CPP
|
||||
- Standalone Mono
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: game
|
||||
attributes:
|
||||
label: Which game did this occur on?
|
||||
description: Please tell us the name of the game. If it's a personal or private project, just let us know the Unity version.
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: what-happened
|
||||
attributes:
|
||||
label: Describe the issue.
|
||||
description: What happened? Should something else have happened instead? Please provide steps to reproduce the issue if possible.
|
||||
placeholder: Tell us what you see!
|
||||
validations:
|
||||
required: true
|
||||
- type: textarea
|
||||
id: logs
|
||||
attributes:
|
||||
label: Relevant log output
|
||||
description: |
|
||||
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: false
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1 @@
|
||||
blank_issues_enabled: false
|
18
.github/ISSUE_TEMPLATE/enhancement.yaml
vendored
Normal file
18
.github/ISSUE_TEMPLATE/enhancement.yaml
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
name: New feature or enhancement
|
||||
description: Suggest or discuss a feature or enhancement for UnityExplorer
|
||||
title: "[Enhancement]: "
|
||||
labels: [enhancement]
|
||||
body:
|
||||
- type: markdown
|
||||
attributes:
|
||||
value: |
|
||||
Thanks for taking the time to discuss UnityExplorer, please provide as much detail as possible.
|
||||
- type: textarea
|
||||
id: description
|
||||
attributes:
|
||||
label: Describe the new feature or enhancement
|
||||
description: |
|
||||
Please go into as much detail as necessary in describing the new feature or enhancement.
|
||||
If providing examples or suggestions for the required C# code, please use syntax-highlighted code blocks.
|
||||
validations:
|
||||
required: true
|
13
.github/ISSUE_TEMPLATE/other.yaml
vendored
Normal file
13
.github/ISSUE_TEMPLATE/other.yaml
vendored
Normal 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
|
20
.github/workflows/dotnet.yml
vendored
20
.github/workflows/dotnet.yml
vendored
@ -28,15 +28,13 @@ jobs:
|
||||
nuget-api-key: ${{ secrets.NuGetAPIKey }}
|
||||
nuget-version: '5.x'
|
||||
|
||||
# Build Il2CppAssemblyUnhollower
|
||||
- run: msbuild lib\Il2CppAssemblyUnhollower\UnhollowerBaseLib\UnhollowerBaseLib.csproj -t:Restore -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release
|
||||
|
||||
# Build mcs
|
||||
- run: nuget restore lib\mcs-unity\mcs.sln
|
||||
- run: msbuild lib\mcs-unity\mcs\mcs.csproj -t:Restore -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release
|
||||
|
||||
# Build UnityExplorer releases, and upload artifacts
|
||||
|
||||
- run: dotnet nuget add source https://nuget.bepinex.dev/v3/index.json
|
||||
- run: nuget restore src\UnityExplorer.sln
|
||||
|
||||
# BepInEx Il2Cpp
|
||||
@ -79,22 +77,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
|
||||
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,6 +1,3 @@
|
||||
[submodule "lib/Il2CppAssemblyUnhollower"]
|
||||
path = lib/Il2CppAssemblyUnhollower
|
||||
url = https://github.com/knah/Il2CppAssemblyUnhollower
|
||||
[submodule "lib/mcs-unity"]
|
||||
path = lib/mcs-unity
|
||||
url = https://github.com/sinai-dev/mcs-unity
|
||||
|
71
README.md
71
README.md
@ -9,16 +9,15 @@
|
||||
✔️ Supports most Unity versions from 5.2 to 2021+ (IL2CPP and Mono).
|
||||
</p>
|
||||
<p align="center">
|
||||
☕ Enjoy this tool? Consider supporting me on <a href="https://ko-fi.com/sinaidev">ko-fi</a>!
|
||||
</p>
|
||||
<p align="center">
|
||||
⚡ UnityExplorer is on <a href="https://thunderstore.io/package/sinai-dev/UnityExplorer/">Thunderstore</a>! (and as <a href="https://gtfo.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP/">IL2CPP</a>)
|
||||
✨ Powered by <a href="https://github.com/sinai-dev/UniverseLib">UniverseLib</a>
|
||||
</p>
|
||||
|
||||
# Releases [](../../releases)
|
||||
|
||||
[](../../releases/latest) [](https://github.com/sinai-dev/UnityExplorer/actions) [](../../releases/latest)
|
||||
|
||||
⚡ Thunderstore releases: [BepInEx Mono](https://thunderstore.io/package/sinai-dev/UnityExplorer) | [BepInEx IL2CPP](https://gtfo.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP) | [MelonLoader IL2CPP](https://boneworks.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP_ML)
|
||||
|
||||
## BepInEx
|
||||
|
||||
| Release | IL2CPP | Mono |
|
||||
@ -26,8 +25,7 @@
|
||||
| BIE 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) |
|
||||
| BIE 5.X | ✖️ n/a | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx5.Mono.zip) |
|
||||
|
||||
1. Take the `UnityExplorer.BIE.[version].dll` file and put it in `BepInEx\plugins\`
|
||||
2. In IL2CPP, you will need to download the [Unity libs](https://github.com/LavaGang/Unity-Runtime-Libraries) for the game's Unity version, create a folder `BepInEx\unity-libs\`, then extract the Unity libs into this folder.
|
||||
1. Take the `UnityExplorer.BIE.[version].dll` and the `UniverseLib.[version].dll` files and put them in `BepInEx\plugins\`
|
||||
|
||||
<i>Note: BepInEx 6 is obtainable via [BepisBuilds](https://builds.bepis.io/projects/bepinex_be)</i>
|
||||
|
||||
@ -35,10 +33,9 @@
|
||||
|
||||
| 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.
|
||||
1. Take the `UnityExplorer.ML.[version].dll` file and put them in the `Mods\` folder created by MelonLoader, and then put the `UniverseLib.[version].dll` file in the `UserLibs\` folder.
|
||||
|
||||
## Standalone
|
||||
|
||||
@ -46,17 +43,28 @@
|
||||
| ------ | ---- |
|
||||
| ✅ [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) |
|
||||
|
||||
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).
|
||||
The standalone release can be used with any injector or loader of your choice, but it requires you to load the dependencies manually.
|
||||
|
||||
1. Load the required libs - HarmonyX, and Il2CppAssemblyUnhollower if IL2CPP
|
||||
1. Ensure the required libs are loaded - UniverseLib, HarmonyX and MonoMod
|
||||
2. For IL2CPP, load Il2CppAssemblyUnhollower and start an [Il2CppAssemblyUnhollower runtime](https://github.com/knah/Il2CppAssemblyUnhollower#required-external-setup)
|
||||
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
|
||||
|
||||
# Known issues
|
||||
* Any `MissingMethodException` or `NotSupportedException`: please report the issue and provide a copy of your mod loader log and/or Unity log.
|
||||
* In IL2CPP, some IEnumerable and IDictionary types may fail enumeration. Waiting for the Unhollower rewrite to address this any further.
|
||||
* The C# Console's completions have some minor issues such as not suggestion global classes which have no namespace, and erronously suggesting classes from using directives when they shouldn't be suggested. These are issues with mcs itself which I am looking into.
|
||||
# Common issues and solutions
|
||||
|
||||
Although UnityExplorer should work out of the box for most Unity games, in some cases you may need to tweak the settings for it to work properly.
|
||||
|
||||
To adjust the settings, open the config file:
|
||||
* BepInEx: `BepInEx\config\com.sinai.unityexplorer.cfg`
|
||||
* MelonLoader: `UserData\MelonPreferences.cfg`
|
||||
* Standalone: `UnityExplorer\config.cfg`
|
||||
|
||||
Try adjusting the following settings and see if it fixes your issues:
|
||||
* `Startup_Delay_Time` - increase to 5-10 seconds (or more as needed), can fix issues with UnityExplorer being destroyed or corrupted during startup.
|
||||
* `Disable_EventSystem_Override` - if input is not working properly, try setting this to `true`.
|
||||
|
||||
If these fixes do not work, please create an issue in this repo and I'll do my best to look into it.
|
||||
|
||||
# Features
|
||||
|
||||
@ -94,39 +102,50 @@ The inspector is used to see detailed information on objects of any type and man
|
||||
* You can execute a script automatically on startup by naming it `startup.cs` and placing it in the `UnityExplorer\Scripts\` folder (this folder will be created where you placed the DLL file).
|
||||
* See the "Help" dropdown in the C# console menu for more detailed information.
|
||||
|
||||
### Hook Manager
|
||||
|
||||
* The Hooks panel allows you to hook methods at the click of a button for debugging purposes.
|
||||
* Simply enter any class (generic types not yet supported) and hook the methods you want from the menu.
|
||||
* You can edit the source code of the generated hook with the "Edit Hook Source" button. Accepted method names are `Prefix` (which can return `bool` or `void`), `Postfix`, `Finalizer` (which can return `Exception` or `void`), and `Transpiler` (which must return `IEnumerable<HarmonyLib.CodeInstruction>`). You can define multiple patches if you wish.
|
||||
|
||||
### Mouse-Inspect
|
||||
|
||||
* The "Mouse Inspect" dropdown on the main UnityExplorer navbar allows you to inspect objects under the mouse.
|
||||
* The "Mouse Inspect" dropdown in the "Inspector" panel allows you to inspect objects under the mouse.
|
||||
* <b>World</b>: uses Physics.Raycast to look for Colliders
|
||||
* <b>UI</b>: uses GraphicRaycasters to find UI objects
|
||||
|
||||
### Clipboard
|
||||
|
||||
* The "Clipboard" panel allows you to see your current paste value, or clear it (resets it to `null`)
|
||||
* Can copy the value from any member in a Reflection Inspector, Enumerable or Dictionary, and from the target of any Inspector tab
|
||||
* Can paste values onto any member in a Reflection Inspector
|
||||
* Non-parsable arguments in Method/Property Evaluators allow pasting values
|
||||
* The C# Console has helper methods `Copy(obj)` and `Paste()` for accessing the Clipboard
|
||||
|
||||
### Settings
|
||||
|
||||
* You can change the settings via the "Options" tab of the menu, or directly from the config file.
|
||||
* BepInEx: `BepInEx\config\com.sinai.unityexplorer.cfg`
|
||||
* MelonLoader: `UserData\MelonPreferences.cfg`
|
||||
* Standalone `{DLL_location}\UnityExplorer\config.ini`
|
||||
* Standalone `{DLL_location}\UnityExplorer\config.cfg`
|
||||
|
||||
# Building
|
||||
|
||||
0. Clone the repository and run `git submodule update --init --recursive` to get the submodules.
|
||||
1. Open the `src\UnityExplorer.sln` project.
|
||||
2. Build `mcs` (Release/AnyCPU, you may need to run `nuget restore mcs.sln`)
|
||||
3. Build the UnityExplorer release(s) you want to use, either by selecting the config as the Active Config, or batch-building.
|
||||
|
||||
If you fork the repository on GitHub you can build using the [dotnet workflow](https://github.com/sinai-dev/UnityExplorer/blob/master/.github/workflows/dotnet.yml):
|
||||
|
||||
0. Click on the Actions tab and enable workflows in your repository
|
||||
1. Click on the "Build UnityExplorer" workflow, then click "Run Workflow" and run it manually, or make a new commit to trigger the workflow.
|
||||
2. Take the artifact from the completed run.
|
||||
|
||||
For Visual Studio:
|
||||
|
||||
0. Clone the repository and run `git submodule update --init --recursive` to get the submodules.
|
||||
1. Open the `src\UnityExplorer.sln` project.
|
||||
2. Build `mcs` (Release/AnyCPU, you may need to run `nuget restore mcs.sln`), and if using IL2CPP then build `Il2CppAssemblyUnhollower` (Release/AnyCPU) as well.
|
||||
3. Build the UnityExplorer release(s) you want to use, either by selecting the config as the Active Config, or batch-building.
|
||||
|
||||
# Acknowledgments
|
||||
|
||||
* [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.
|
||||
* [Geoffrey Horsington](https://github.com/ghorsington) for [mcs-unity](https://github.com/sinai-dev/mcs-unity) \[no license\], used as the `Mono.CSharp` reference for the C# Console.
|
||||
|
||||
### Disclaimer
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Submodule lib/Il2CppAssemblyUnhollower deleted from 0911fdaca6
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
lib/unhollowed/UnityEngine.AssetBundleModule.dll
Normal file
BIN
lib/unhollowed/UnityEngine.AssetBundleModule.dll
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -3,10 +3,15 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.UI.CSConsole.Lexers;
|
||||
using UnityExplorer.CSConsole.Lexers;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public class CSAutoCompleter : ISuggestionProvider
|
||||
{
|
||||
@ -22,7 +27,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
AutoCompleteModal.Instance.ReleaseOwnership(this);
|
||||
}
|
||||
|
||||
private readonly HashSet<char> delimiters = new HashSet<char>
|
||||
private readonly HashSet<char> delimiters = new()
|
||||
{
|
||||
'{', '}', ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&', '?'
|
||||
};
|
@ -9,12 +9,17 @@ using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.UI.CSConsole;
|
||||
using UniverseLib.Input;
|
||||
using UnityExplorer.CSConsole;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public static class ConsoleController
|
||||
{
|
||||
@ -24,6 +29,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
|
||||
private static HashSet<string> usingDirectives;
|
||||
private static StringBuilder evaluatorOutput;
|
||||
private static StringWriter evaluatorStringWriter;
|
||||
|
||||
public static CSConsolePanel Panel => UIManager.GetPanel<CSConsolePanel>(UIManager.Panels.CSConsole);
|
||||
public static InputFieldRef Input => Panel.Input;
|
||||
@ -43,6 +49,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
"System",
|
||||
"System.Linq",
|
||||
"System.Text",
|
||||
"System.Collections",
|
||||
"System.Collections.Generic",
|
||||
"UnityEngine",
|
||||
#if CPP
|
||||
@ -103,7 +110,6 @@ namespace UnityExplorer.UI.CSConsole
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region UI Listeners and options
|
||||
|
||||
// TODO save
|
||||
@ -128,6 +134,12 @@ namespace UnityExplorer.UI.CSConsole
|
||||
|
||||
#region Evaluating
|
||||
|
||||
private static void GenerateTextWriter()
|
||||
{
|
||||
evaluatorOutput = new StringBuilder();
|
||||
evaluatorStringWriter = new StringWriter(evaluatorOutput);
|
||||
}
|
||||
|
||||
public static void ResetConsole() => ResetConsole(true);
|
||||
|
||||
public static void ResetConsole(bool logSuccess = true)
|
||||
@ -138,8 +150,8 @@ namespace UnityExplorer.UI.CSConsole
|
||||
if (Evaluator != null)
|
||||
Evaluator.Dispose();
|
||||
|
||||
evaluatorOutput = new StringBuilder();
|
||||
Evaluator = new ScriptEvaluator(new StringWriter(evaluatorOutput))
|
||||
GenerateTextWriter();
|
||||
Evaluator = new ScriptEvaluator(evaluatorStringWriter)
|
||||
{
|
||||
InteractiveBaseClass = typeof(ScriptInteraction)
|
||||
};
|
||||
@ -174,6 +186,12 @@ namespace UnityExplorer.UI.CSConsole
|
||||
if (SRENotSupported)
|
||||
return;
|
||||
|
||||
if (evaluatorStringWriter == null || evaluatorOutput == null)
|
||||
{
|
||||
GenerateTextWriter();
|
||||
Evaluator._textWriter = evaluatorStringWriter;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// Compile the code. If it returned a CompiledMethod, it is REPL.
|
||||
@ -201,7 +219,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
{
|
||||
// The compiled code was not REPL, so it was a using directive or it defined classes.
|
||||
|
||||
string output = ScriptEvaluator._textWriter.ToString();
|
||||
string output = Evaluator._textWriter.ToString();
|
||||
var outputSplit = output.Split('\n');
|
||||
if (outputSplit.Length >= 2)
|
||||
output = outputSplit[outputSplit.Length - 2];
|
||||
@ -341,7 +359,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
var charTop = charInfo.cursorPos.y;
|
||||
var charBot = charTop - CSCONSOLE_LINEHEIGHT;
|
||||
|
||||
var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
|
||||
var viewportMin = Input.Transform.rect.height - Input.Transform.anchoredPosition.y - (Input.Transform.rect.height * 0.5f);
|
||||
var viewportMax = viewportMin - Panel.InputScroller.ViewportRect.rect.height;
|
||||
|
||||
float diff = 0f;
|
||||
@ -352,7 +370,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
|
||||
if (Math.Abs(diff) > 1)
|
||||
{
|
||||
var rect = Input.Rect;
|
||||
var rect = Input.Transform;
|
||||
rect.anchoredPosition = new Vector2(rect.anchoredPosition.x, rect.anchoredPosition.y - diff);
|
||||
}
|
||||
}
|
||||
@ -362,7 +380,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
{
|
||||
settingCaretCoroutine = true;
|
||||
Input.Component.readOnly = true;
|
||||
RuntimeProvider.Instance.StartCoroutine(SetCaretCoroutine(caretPosition));
|
||||
RuntimeHelper.StartCoroutine(SetCaretCoroutine(caretPosition));
|
||||
}
|
||||
|
||||
internal static PropertyInfo SelectionGuardProperty => selectionGuardPropInfo ?? GetSelectionGuardPropInfo();
|
||||
@ -422,7 +440,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
|
||||
// the top and bottom position of the viewport in relation to the text height
|
||||
// they need the half-height adjustment to normalize against the 'line.topY' value.
|
||||
var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
|
||||
var viewportMin = Input.Transform.rect.height - Input.Transform.anchoredPosition.y - (Input.Transform.rect.height * 0.5f);
|
||||
var viewportMax = viewportMin - Panel.InputScroller.ViewportRect.rect.height;
|
||||
|
||||
for (int i = 0; i < Input.TextGenerator.lineCount; i++)
|
||||
@ -587,7 +605,7 @@ If the game was built with Unity's stubbed netstandard 2.0 runtime, you can fix
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, string> helpDict = new Dictionary<string, string>();
|
||||
private static readonly Dictionary<string, string> helpDict = new();
|
||||
|
||||
public static void SetupHelpInteraction()
|
||||
{
|
||||
@ -642,15 +660,17 @@ var x = 5;
|
||||
++x;
|
||||
|
||||
/* The following helpers are available in REPL mode:
|
||||
* GetUsing(); - prints the current using directives to the console log
|
||||
* GetVars(); - prints the names and values of the REPL variables you have defined
|
||||
* GetClasses(); - prints the names and members of the classes you have defined
|
||||
* Log(obj); - prints a message to the console log
|
||||
* CurrentTarget; - System.Object, the target of the active Inspector tab
|
||||
* AllTargets; - System.Object[], the targets of all Inspector tabs
|
||||
* Log(obj); - prints a message to the console log
|
||||
* Inspect(obj); - inspect the object with the Inspector
|
||||
* Inspect(someType); - inspect a Type with static reflection
|
||||
* Start(enumerator); - starts the IEnumerator as a Coroutine
|
||||
* Copy(obj); - copies the object to the UnityExplorer Clipboard
|
||||
* Paste(); - System.Object, the contents of the Clipboard.
|
||||
* GetUsing(); - prints the current using directives to the console log
|
||||
* GetVars(); - prints the names and values of the REPL variables you have defined
|
||||
* GetClasses(); - prints the names and members of the classes you have defined
|
||||
* help; - the default REPL help command, contains additional helpers.
|
||||
*/";
|
||||
|
||||
@ -658,7 +678,7 @@ var x = 5;
|
||||
// You can soft-overwrite a class by compiling it again with the same name. The old class will still technically exist in memory.
|
||||
|
||||
// Compiled classes can be accessed from both inside and outside this console.
|
||||
// Note: in IL2CPP, injecting these classes with ClassInjector may crash the game!
|
||||
// Note: in IL2CPP, you must declare a Namespace to inject these classes with ClassInjector or it will crash the game.
|
||||
|
||||
public class HelloWorld
|
||||
{
|
@ -5,9 +5,11 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CSConsole.Lexers;
|
||||
using UnityExplorer.CSConsole.Lexers;
|
||||
using UniverseLib;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public struct MatchInfo
|
||||
{
|
||||
@ -121,16 +123,15 @@ namespace UnityExplorer.UI.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
|
@ -2,7 +2,7 @@
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole.Lexers
|
||||
namespace UnityExplorer.CSConsole.Lexers
|
||||
{
|
||||
public class CommentLexer : Lexer
|
||||
{
|
@ -2,7 +2,7 @@
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole.Lexers
|
||||
namespace UnityExplorer.CSConsole.Lexers
|
||||
{
|
||||
public class KeywordLexer : Lexer
|
||||
{
|
@ -1,8 +1,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UniverseLib;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole.Lexers
|
||||
namespace UnityExplorer.CSConsole.Lexers
|
||||
{
|
||||
public abstract class Lexer
|
||||
{
|
@ -1,6 +1,6 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole.Lexers
|
||||
namespace UnityExplorer.CSConsole.Lexers
|
||||
{
|
||||
public class NumberLexer : Lexer
|
||||
{
|
@ -1,7 +1,7 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole.Lexers
|
||||
namespace UnityExplorer.CSConsole.Lexers
|
||||
{
|
||||
public class StringLexer : Lexer
|
||||
{
|
@ -3,7 +3,7 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole.Lexers
|
||||
namespace UnityExplorer.CSConsole.Lexers
|
||||
{
|
||||
public class SymbolLexer : Lexer
|
||||
{
|
@ -7,7 +7,7 @@ using System.Text;
|
||||
|
||||
// Thanks to ManlyMarco for this
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public class ScriptEvaluator : Evaluator, IDisposable
|
||||
{
|
||||
@ -16,7 +16,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
"mscorlib", "System.Core", "System", "System.Xml"
|
||||
};
|
||||
|
||||
internal static TextWriter _textWriter;
|
||||
internal TextWriter _textWriter;
|
||||
internal static StreamReportPrinter _reportPrinter;
|
||||
|
||||
public ScriptEvaluator(TextWriter tw) : base(BuildContext(tw))
|
||||
@ -51,8 +51,13 @@ namespace UnityExplorer.UI.CSConsole
|
||||
ReferenceAssembly(asm);
|
||||
}
|
||||
|
||||
private static CompilerContext context;
|
||||
|
||||
private static CompilerContext BuildContext(TextWriter tw)
|
||||
{
|
||||
if (context != null)
|
||||
return context;
|
||||
|
||||
_reportPrinter = new StreamReportPrinter(tw);
|
||||
|
||||
var settings = new CompilerSettings
|
||||
@ -65,7 +70,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
EnhancedWarnings = false
|
||||
};
|
||||
|
||||
return new CompilerContext(settings, _reportPrinter);
|
||||
return context = new CompilerContext(settings, _reportPrinter);
|
||||
}
|
||||
|
||||
private static void ImportAppdomainAssemblies(Action<Assembly> import)
|
@ -1,50 +1,45 @@
|
||||
using Mono.CSharp;
|
||||
using HarmonyLib;
|
||||
using Mono.CSharp;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.Runtime;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UniverseLib;
|
||||
|
||||
/*
|
||||
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.UI.CSConsole
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public class ScriptInteraction : InteractiveBase
|
||||
{
|
||||
public static object CurrentTarget
|
||||
=> InspectorManager.ActiveInspector?.Target;
|
||||
|
||||
public static object[] AllTargets
|
||||
=> InspectorManager.Inspectors.Select(it => it.Target).ToArray();
|
||||
|
||||
public static void Log(object message)
|
||||
{
|
||||
ExplorerCore.Log(message);
|
||||
}
|
||||
|
||||
public static object CurrentTarget => InspectorManager.ActiveInspector?.Target;
|
||||
|
||||
public static object[] AllTargets => InspectorManager.Inspectors.Select(it => it.Target).ToArray();
|
||||
=> ExplorerCore.Log(message);
|
||||
|
||||
public static void Inspect(object obj)
|
||||
{
|
||||
InspectorManager.Inspect(obj);
|
||||
}
|
||||
=> InspectorManager.Inspect(obj);
|
||||
|
||||
public static void Inspect(Type type)
|
||||
{
|
||||
InspectorManager.Inspect(type);
|
||||
}
|
||||
=> InspectorManager.Inspect(type);
|
||||
|
||||
public static void Start(IEnumerator ienumerator)
|
||||
{
|
||||
RuntimeProvider.Instance.StartCoroutine(ienumerator);
|
||||
}
|
||||
public static void Start(IEnumerator ienumerator)
|
||||
=> RuntimeHelper.StartCoroutine(ienumerator);
|
||||
|
||||
public static void Copy(object obj)
|
||||
=> ClipboardPanel.Copy(obj);
|
||||
|
||||
public static object Paste()
|
||||
=> ClipboardPanel.Current;
|
||||
|
||||
public static void GetUsing()
|
||||
{
|
||||
Log(Evaluator.GetUsing());
|
||||
}
|
||||
=> Log(Evaluator.GetUsing());
|
||||
|
||||
public static void GetVars()
|
||||
{
|
||||
@ -57,7 +52,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
|
||||
public static void GetClasses()
|
||||
{
|
||||
if (ReflectionUtility.GetFieldInfo(typeof(Evaluator), "source_file")
|
||||
if (AccessTools.Field(typeof(Evaluator), "source_file")
|
||||
.GetValue(Evaluator) is CompilationSourceFile sourceFile
|
||||
&& sourceFile.Containers.Any())
|
||||
{
|
||||
@ -76,4 +71,4 @@ namespace UnityExplorer.UI.CSConsole
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -2,21 +2,21 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.Config;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject
|
||||
namespace UnityExplorer.CacheObject
|
||||
{
|
||||
public class CacheConfigEntry : CacheObjectBase
|
||||
{
|
||||
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;
|
||||
}
|
||||
@ -44,6 +44,6 @@ namespace UnityExplorer.UI.CacheObject
|
||||
RefConfigElement.BoxedValue = value;
|
||||
}
|
||||
|
||||
protected override bool SetCellEvaluateState(CacheObjectCell cell) => false;
|
||||
protected override bool TryAutoEvaluateIfUnitialized(CacheObjectCell cell) => true;
|
||||
}
|
||||
}
|
@ -3,9 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.Inspectors;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject
|
||||
namespace UnityExplorer.CacheObject
|
||||
{
|
||||
public class CacheField : CacheMember
|
||||
{
|
@ -2,10 +2,12 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.UI.CacheObject.IValues;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.CacheObject.IValues;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
using UniverseLib;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject
|
||||
namespace UnityExplorer.CacheObject
|
||||
{
|
||||
public class CacheKeyValuePair : CacheObjectBase
|
||||
{
|
||||
@ -61,6 +63,7 @@ namespace UnityExplorer.UI.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)
|
||||
@ -90,10 +93,6 @@ namespace UnityExplorer.UI.CacheObject
|
||||
}
|
||||
|
||||
|
||||
protected override bool SetCellEvaluateState(CacheObjectCell cell)
|
||||
{
|
||||
// not needed
|
||||
return false;
|
||||
}
|
||||
protected override bool TryAutoEvaluateIfUnitialized(CacheObjectCell cell) => true;
|
||||
}
|
||||
}
|
@ -2,10 +2,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.UI.CacheObject.IValues;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.CacheObject.IValues;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject
|
||||
namespace UnityExplorer.CacheObject
|
||||
{
|
||||
public class CacheListEntry : CacheObjectBase
|
||||
{
|
||||
@ -13,7 +13,7 @@ namespace UnityExplorer.UI.CacheObject
|
||||
|
||||
public override bool ShouldAutoEvaluate => true;
|
||||
public override bool HasArguments => false;
|
||||
public override bool CanWrite => Owner.CanWrite;
|
||||
public override bool CanWrite => Owner?.CanWrite ?? false;
|
||||
|
||||
public void SetListOwner(InteractiveList list, int listIndex)
|
||||
{
|
||||
@ -28,6 +28,7 @@ namespace UnityExplorer.UI.CacheObject
|
||||
var listCell = cell as CacheListEntryCell;
|
||||
|
||||
listCell.NameLabel.text = $"{ListIndex}:";
|
||||
listCell.HiddenNameLabel.Text = "";
|
||||
listCell.Image.color = ListIndex % 2 == 0 ? CacheListEntryCell.EvenColor : CacheListEntryCell.OddColor;
|
||||
}
|
||||
|
||||
@ -36,11 +37,6 @@ namespace UnityExplorer.UI.CacheObject
|
||||
(Owner as InteractiveList).TrySetValueToIndex(value, this.ListIndex);
|
||||
}
|
||||
|
||||
|
||||
protected override bool SetCellEvaluateState(CacheObjectCell cell)
|
||||
{
|
||||
// not needed
|
||||
return false;
|
||||
}
|
||||
protected override bool TryAutoEvaluateIfUnitialized(CacheObjectCell cell) => true;
|
||||
}
|
||||
}
|
@ -4,18 +4,21 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.Runtime;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UniverseLib.UI.Models;
|
||||
using UnityExplorer.UI;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib.Utility;
|
||||
using UniverseLib.UI.ObjectPool;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject
|
||||
namespace UnityExplorer.CacheObject
|
||||
{
|
||||
public abstract class CacheMember : CacheObjectBase
|
||||
{
|
||||
//public ReflectionInspector ParentInspector { get; internal set; }
|
||||
//public bool AutoUpdateWanted { get; internal set; }
|
||||
|
||||
public abstract Type DeclaringType { get; }
|
||||
public string NameForFiltering { get; protected set; }
|
||||
public object DeclaringInstance => IsStatic ? null : (m_declaringInstance ?? (m_declaringInstance = Owner.Target.TryCast(DeclaringType)));
|
||||
@ -31,8 +34,11 @@ namespace UnityExplorer.UI.CacheObject
|
||||
public virtual void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member)
|
||||
{
|
||||
this.Owner = inspector;
|
||||
this.NameLabelText = SignatureHighlighter.Parse(member.DeclaringType, false, member);
|
||||
this.NameLabelText = this is CacheMethod
|
||||
? SignatureHighlighter.HighlightMethod(member as MethodInfo)
|
||||
: SignatureHighlighter.Parse(member.DeclaringType, false, member);
|
||||
this.NameForFiltering = $"{member.DeclaringType.Name}.{member.Name}";
|
||||
this.NameLabelTextRaw = NameForFiltering;
|
||||
}
|
||||
|
||||
public override void ReleasePooledObjects()
|
||||
@ -59,6 +65,17 @@ namespace UnityExplorer.UI.CacheObject
|
||||
|
||||
protected abstract void TrySetValue(object value);
|
||||
|
||||
/// <summary>
|
||||
/// Evaluate is called when first shown (if ShouldAutoEvaluate), or else when Evaluate button is clicked, or auto-updated.
|
||||
/// </summary>
|
||||
public void Evaluate()
|
||||
{
|
||||
SetValueFromSource(TryEvaluate());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Called when user presses the Evaluate button.
|
||||
/// </summary>
|
||||
public void EvaluateAndSetCell()
|
||||
{
|
||||
Evaluate();
|
||||
@ -66,40 +83,27 @@ namespace UnityExplorer.UI.CacheObject
|
||||
SetDataToCell(CellView);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Evaluate when first shown (if ShouldAutoEvaluate), or else when Evaluate button is clicked, or auto-updated.
|
||||
/// </summary>
|
||||
public void Evaluate()
|
||||
{
|
||||
SetValueFromSource(TryEvaluate());
|
||||
}
|
||||
|
||||
public override void TrySetUserValue(object value)
|
||||
{
|
||||
TrySetValue(value);
|
||||
|
||||
Evaluate();
|
||||
}
|
||||
|
||||
protected override void SetValueState(CacheObjectCell cell, ValueStateArgs args)
|
||||
{
|
||||
base.SetValueState(cell, args);
|
||||
|
||||
//var memCell = cell as CacheMemberCell;
|
||||
//memCell.UpdateToggle.gameObject.SetActive(ShouldAutoEvaluate);
|
||||
}
|
||||
|
||||
private static readonly Color evalEnabledColor = new Color(0.15f, 0.25f, 0.15f);
|
||||
private static readonly Color evalDisabledColor = new Color(0.15f, 0.15f, 0.15f);
|
||||
|
||||
protected override bool SetCellEvaluateState(CacheObjectCell objectcell)
|
||||
protected override bool TryAutoEvaluateIfUnitialized(CacheObjectCell objectcell)
|
||||
{
|
||||
var cell = objectcell as CacheMemberCell;
|
||||
|
||||
cell.EvaluateHolder.SetActive(!ShouldAutoEvaluate);
|
||||
if (!ShouldAutoEvaluate)
|
||||
{
|
||||
//cell.UpdateToggle.gameObject.SetActive(false);
|
||||
cell.EvaluateButton.Component.gameObject.SetActive(true);
|
||||
if (HasArguments)
|
||||
{
|
||||
@ -109,36 +113,30 @@ namespace UnityExplorer.UI.CacheObject
|
||||
{
|
||||
cell.EvaluateButton.ButtonText.text = "Hide";
|
||||
Evaluator.UIRoot.transform.SetParent(cell.EvaluateHolder.transform, false);
|
||||
RuntimeProvider.Instance.SetColorBlock(cell.EvaluateButton.Component, evalEnabledColor, evalEnabledColor * 1.3f);
|
||||
RuntimeHelper.SetColorBlock(cell.EvaluateButton.Component, evalEnabledColor, evalEnabledColor * 1.3f);
|
||||
}
|
||||
}
|
||||
else
|
||||
cell.EvaluateButton.ButtonText.text = "Evaluate";
|
||||
|
||||
if (!Evaluating)
|
||||
RuntimeProvider.Instance.SetColorBlock(cell.EvaluateButton.Component, evalDisabledColor, evalDisabledColor * 1.3f);
|
||||
RuntimeHelper.SetColorBlock(cell.EvaluateButton.Component, evalDisabledColor, evalDisabledColor * 1.3f);
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// cell.UpdateToggle.gameObject.SetActive(true);
|
||||
// cell.UpdateToggle.isOn = AutoUpdateWanted;
|
||||
//}
|
||||
|
||||
if (State == ValueState.NotEvaluated && !ShouldAutoEvaluate)
|
||||
{
|
||||
SetValueState(cell, ValueStateArgs.Default);
|
||||
cell.RefreshSubcontentButton();
|
||||
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (State == ValueState.NotEvaluated)
|
||||
Evaluate();
|
||||
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public void OnEvaluateClicked()
|
||||
{
|
||||
if (!HasArguments)
|
||||
@ -152,7 +150,7 @@ namespace UnityExplorer.UI.CacheObject
|
||||
this.Evaluator = Pool<EvaluateWidget>.Borrow();
|
||||
Evaluator.OnBorrowedFromPool(this);
|
||||
Evaluator.UIRoot.transform.SetParent((CellView as CacheMemberCell).EvaluateHolder.transform, false);
|
||||
SetCellEvaluateState(CellView);
|
||||
TryAutoEvaluateIfUnitialized(CellView);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -161,7 +159,7 @@ namespace UnityExplorer.UI.CacheObject
|
||||
else
|
||||
Evaluator.UIRoot.SetActive(true);
|
||||
|
||||
SetCellEvaluateState(CellView);
|
||||
TryAutoEvaluateIfUnitialized(CellView);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -169,22 +167,22 @@ namespace UnityExplorer.UI.CacheObject
|
||||
|
||||
#region Cache Member Util
|
||||
|
||||
public static bool CanParseArgs(ParameterInfo[] parameters)
|
||||
{
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
var pType = param.ParameterType;
|
||||
|
||||
if (pType.IsByRef && pType.HasElementType)
|
||||
pType = pType.GetElementType();
|
||||
|
||||
if (pType != null && ParseUtility.CanParse(pType))
|
||||
continue;
|
||||
else
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
//public static bool CanParseArgs(ParameterInfo[] parameters)
|
||||
//{
|
||||
// foreach (var param in parameters)
|
||||
// {
|
||||
// var pType = param.ParameterType;
|
||||
//
|
||||
// if (pType.IsByRef && pType.HasElementType)
|
||||
// pType = pType.GetElementType();
|
||||
//
|
||||
// if (pType != null && ParseUtility.CanParse(pType))
|
||||
// continue;
|
||||
// else
|
||||
// return false;
|
||||
// }
|
||||
// return true;
|
||||
//}
|
||||
|
||||
public static List<CacheMember> GetCacheMembers(object inspectorTarget, Type _type, ReflectionInspector _inspector)
|
||||
{
|
||||
@ -239,12 +237,12 @@ namespace UnityExplorer.UI.CacheObject
|
||||
{
|
||||
try
|
||||
{
|
||||
if (ReflectionUtility.IsBlacklisted(member))
|
||||
if (UERuntimeHelper.IsBlacklisted(member))
|
||||
return;
|
||||
|
||||
var sig = GetSig(member);
|
||||
|
||||
//ExplorerCore.Log($"Trying to cache member {sig}...");
|
||||
//ExplorerCore.Log($"Trying to cache member {sig}... ({member.MemberType})");
|
||||
|
||||
CacheMember cached;
|
||||
Type returnType;
|
||||
@ -257,11 +255,11 @@ namespace UnityExplorer.UI.CacheObject
|
||||
&& (mi.Name.StartsWith("get_") || mi.Name.StartsWith("set_")))
|
||||
return;
|
||||
|
||||
var args = mi.GetParameters();
|
||||
if (!CanParseArgs(args))
|
||||
return;
|
||||
//var args = mi.GetParameters();
|
||||
//if (!CanParseArgs(args))
|
||||
// return;
|
||||
|
||||
sig += GetArgumentString(args);
|
||||
sig += GetArgumentString(mi.GetParameters());
|
||||
if (cachedSigs.Contains(sig))
|
||||
return;
|
||||
|
||||
@ -274,9 +272,9 @@ namespace UnityExplorer.UI.CacheObject
|
||||
{
|
||||
var pi = member as PropertyInfo;
|
||||
|
||||
var args = pi.GetIndexParameters();
|
||||
if (!CanParseArgs(args))
|
||||
return;
|
||||
//var args = pi.GetIndexParameters();
|
||||
//if (!CanParseArgs(args))
|
||||
// return;
|
||||
|
||||
if (!pi.CanRead && pi.CanWrite)
|
||||
{
|
||||
@ -287,7 +285,7 @@ namespace UnityExplorer.UI.CacheObject
|
||||
return;
|
||||
}
|
||||
|
||||
sig += GetArgumentString(args);
|
||||
sig += GetArgumentString(pi.GetIndexParameters());
|
||||
if (cachedSigs.Contains(sig))
|
||||
return;
|
||||
|
||||
@ -309,7 +307,6 @@ namespace UnityExplorer.UI.CacheObject
|
||||
|
||||
cachedSigs.Add(sig);
|
||||
|
||||
//cached.Initialize(_inspector, declaringType, member, returnType);
|
||||
cached.SetFallbackType(returnType);
|
||||
cached.SetInspectorOwner(_inspector, member);
|
||||
|
@ -3,9 +3,11 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UniverseLib;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject
|
||||
namespace UnityExplorer.CacheObject
|
||||
{
|
||||
public class CacheMethod : CacheMember
|
||||
{
|
||||
@ -30,15 +32,14 @@ namespace UnityExplorer.UI.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;
|
@ -6,12 +6,17 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.UI.CacheObject.IValues;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.Runtime;
|
||||
using UnityExplorer.CacheObject.IValues;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
using UniverseLib.UI.Models;
|
||||
using UnityExplorer.UI;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.Utility;
|
||||
using UniverseLib.UI.ObjectPool;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject
|
||||
namespace UnityExplorer.CacheObject
|
||||
{
|
||||
public enum ValueState
|
||||
{
|
||||
@ -46,6 +51,7 @@ namespace UnityExplorer.UI.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; }
|
||||
@ -216,10 +222,7 @@ namespace UnityExplorer.UI.CacheObject
|
||||
// string wants it trimmed to max 200 chars
|
||||
case ValueState.String:
|
||||
if (!LastValueWasNull)
|
||||
{
|
||||
string s = Value as string;
|
||||
return $"\"{ToStringUtility.PruneString(s, 200, 5)}\"";
|
||||
}
|
||||
return $"\"{ToStringUtility.PruneString(Value as string, 200, 5)}\"";
|
||||
break;
|
||||
|
||||
// try to prefix the count of the collection for lists and dicts
|
||||
@ -253,12 +256,14 @@ namespace UnityExplorer.UI.CacheObject
|
||||
|
||||
// Setting cell state from our model
|
||||
|
||||
/// <summary>Return true if SetCell should abort, false if it should continue.</summary>
|
||||
protected abstract bool SetCellEvaluateState(CacheObjectCell cell);
|
||||
/// <summary>Return false if SetCell should abort, true if it should continue.</summary>
|
||||
protected abstract bool TryAutoEvaluateIfUnitialized(CacheObjectCell cell);
|
||||
|
||||
public virtual void SetDataToCell(CacheObjectCell cell)
|
||||
{
|
||||
cell.NameLabel.text = NameLabelText;
|
||||
if (cell.HiddenNameLabel != null)
|
||||
cell.HiddenNameLabel.Text = NameLabelTextRaw ?? string.Empty;
|
||||
cell.ValueLabel.gameObject.SetActive(true);
|
||||
|
||||
cell.SubContentHolder.gameObject.SetActive(SubContentShowWanted);
|
||||
@ -268,9 +273,21 @@ namespace UnityExplorer.UI.CacheObject
|
||||
IValue.SetLayout();
|
||||
}
|
||||
|
||||
if (SetCellEvaluateState(cell))
|
||||
bool evaluated = TryAutoEvaluateIfUnitialized(cell);
|
||||
|
||||
if (cell.CopyButton != null)
|
||||
{
|
||||
bool hasEvaluated = State != ValueState.NotEvaluated && State != ValueState.Exception;
|
||||
cell.CopyButton.Component.gameObject.SetActive(hasEvaluated);
|
||||
cell.PasteButton.Component.gameObject.SetActive(hasEvaluated && this.CanWrite);
|
||||
}
|
||||
|
||||
if (!evaluated)
|
||||
return;
|
||||
|
||||
// The following only executes if the object has evaluated.
|
||||
// For members and properties with args, they will return by default now.
|
||||
|
||||
switch (State)
|
||||
{
|
||||
case ValueState.Exception:
|
||||
@ -438,7 +455,7 @@ namespace UnityExplorer.UI.CacheObject
|
||||
{
|
||||
inactiveIValueHolder = new GameObject("Temp_IValue_Holder");
|
||||
GameObject.DontDestroyOnLoad(inactiveIValueHolder);
|
||||
inactiveIValueHolder.transform.parent = UIManager.PoolHolder.transform;
|
||||
inactiveIValueHolder.transform.parent = UniversalUI.PoolHolder.transform;
|
||||
inactiveIValueHolder.SetActive(false);
|
||||
}
|
||||
return inactiveIValueHolder;
|
@ -3,9 +3,9 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.Inspectors;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject
|
||||
namespace UnityExplorer.CacheObject
|
||||
{
|
||||
public class CacheProperty : CacheMember
|
||||
{
|
@ -3,10 +3,10 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject
|
||||
namespace UnityExplorer.CacheObject
|
||||
{
|
||||
public interface ICacheObjectController
|
||||
{
|
@ -4,9 +4,13 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.UI;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public class InteractiveColor : InteractiveValue
|
||||
{
|
||||
@ -14,11 +18,11 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
|
||||
public Color EditedColor;
|
||||
|
||||
private Image m_colorImage;
|
||||
private readonly InputFieldRef[] m_inputs = new InputFieldRef[4];
|
||||
private readonly Slider[] m_sliders = new Slider[4];
|
||||
private Image colorImage;
|
||||
private readonly InputFieldRef[] inputs = new InputFieldRef[4];
|
||||
private readonly Slider[] sliders = new Slider[4];
|
||||
|
||||
private ButtonRef m_applyButton;
|
||||
private ButtonRef applyButton;
|
||||
|
||||
private static readonly string[] fieldNames = new[] { "R", "G", "B", "A" };
|
||||
|
||||
@ -26,11 +30,11 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
{
|
||||
base.OnBorrowed(owner);
|
||||
|
||||
m_applyButton.Component.gameObject.SetActive(owner.CanWrite);
|
||||
applyButton.Component.gameObject.SetActive(owner.CanWrite);
|
||||
|
||||
foreach (var slider in m_sliders)
|
||||
foreach (var slider in sliders)
|
||||
slider.interactable = owner.CanWrite;
|
||||
foreach (var input in m_inputs)
|
||||
foreach (var input in inputs)
|
||||
input.Component.readOnly = !owner.CanWrite;
|
||||
}
|
||||
|
||||
@ -46,27 +50,27 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
{
|
||||
IsValueColor32 = true;
|
||||
EditedColor = c32;
|
||||
m_inputs[0].Text = c32.r.ToString();
|
||||
m_inputs[1].Text = c32.g.ToString();
|
||||
m_inputs[2].Text = c32.b.ToString();
|
||||
m_inputs[3].Text = c32.a.ToString();
|
||||
foreach (var slider in m_sliders)
|
||||
inputs[0].Text = c32.r.ToString();
|
||||
inputs[1].Text = c32.g.ToString();
|
||||
inputs[2].Text = c32.b.ToString();
|
||||
inputs[3].Text = c32.a.ToString();
|
||||
foreach (var slider in sliders)
|
||||
slider.maxValue = 255;
|
||||
}
|
||||
else
|
||||
{
|
||||
IsValueColor32 = false;
|
||||
EditedColor = (Color)value;
|
||||
m_inputs[0].Text = EditedColor.r.ToString();
|
||||
m_inputs[1].Text = EditedColor.g.ToString();
|
||||
m_inputs[2].Text = EditedColor.b.ToString();
|
||||
m_inputs[3].Text = EditedColor.a.ToString();
|
||||
foreach (var slider in m_sliders)
|
||||
inputs[0].Text = EditedColor.r.ToString();
|
||||
inputs[1].Text = EditedColor.g.ToString();
|
||||
inputs[2].Text = EditedColor.b.ToString();
|
||||
inputs[3].Text = EditedColor.a.ToString();
|
||||
foreach (var slider in sliders)
|
||||
slider.maxValue = 1;
|
||||
}
|
||||
|
||||
if (m_colorImage)
|
||||
m_colorImage.color = EditedColor;
|
||||
if (colorImage)
|
||||
colorImage.color = EditedColor;
|
||||
}
|
||||
|
||||
// setting value to owner
|
||||
@ -89,8 +93,8 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
case 3: EditedColor.a = val; break;
|
||||
}
|
||||
|
||||
if (m_colorImage)
|
||||
m_colorImage.color = EditedColor;
|
||||
if (colorImage)
|
||||
colorImage.color = EditedColor;
|
||||
}
|
||||
|
||||
private void OnInputChanged(string val, int fieldIndex)
|
||||
@ -101,13 +105,13 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
if (IsValueColor32)
|
||||
{
|
||||
byte value = byte.Parse(val);
|
||||
m_sliders[fieldIndex].value = value;
|
||||
sliders[fieldIndex].value = value;
|
||||
f = (float)((decimal)value / 255);
|
||||
}
|
||||
else
|
||||
{
|
||||
f = float.Parse(val);
|
||||
m_sliders[fieldIndex].value = f;
|
||||
sliders[fieldIndex].value = f;
|
||||
}
|
||||
|
||||
SetColorField(f, fieldIndex);
|
||||
@ -127,12 +131,12 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
{
|
||||
if (IsValueColor32)
|
||||
{
|
||||
m_inputs[fieldIndex].Text = ((byte)val).ToString();
|
||||
inputs[fieldIndex].Text = ((byte)val).ToString();
|
||||
val /= 255f;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_inputs[fieldIndex].Text = val.ToString();
|
||||
inputs[fieldIndex].Text = val.ToString();
|
||||
}
|
||||
|
||||
SetColorField(val, fieldIndex);
|
||||
@ -165,15 +169,15 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
|
||||
// apply button
|
||||
|
||||
m_applyButton = UIFactory.CreateButton(horiGroup, "ApplyButton", "Apply", new Color(0.2f, 0.26f, 0.2f));
|
||||
UIFactory.SetLayoutElement(m_applyButton.Component.gameObject, minHeight: 25, minWidth: 90);
|
||||
m_applyButton.OnClick += SetValueToOwner;
|
||||
applyButton = UIFactory.CreateButton(horiGroup, "ApplyButton", "Apply", new Color(0.2f, 0.26f, 0.2f));
|
||||
UIFactory.SetLayoutElement(applyButton.Component.gameObject, minHeight: 25, minWidth: 90);
|
||||
applyButton.OnClick += SetValueToOwner;
|
||||
|
||||
// image of color
|
||||
|
||||
var imgObj = UIFactory.CreateUIObject("ColorImageHelper", horiGroup);
|
||||
UIFactory.SetLayoutElement(imgObj, minHeight: 25, minWidth: 50, flexibleWidth: 50);
|
||||
m_colorImage = imgObj.AddComponent<Image>();
|
||||
colorImage = imgObj.AddComponent<Image>();
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
@ -188,11 +192,11 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
|
||||
var input = UIFactory.CreateInputField(row, "Input", "...");
|
||||
UIFactory.SetLayoutElement(input.UIRoot, minWidth: 40, minHeight: 25, flexibleHeight: 0);
|
||||
m_inputs[index] = input;
|
||||
inputs[index] = input;
|
||||
input.OnValueChanged += (string val) => { OnInputChanged(val, index); };
|
||||
|
||||
var sliderObj = UIFactory.CreateSlider(row, "Slider", out Slider slider);
|
||||
m_sliders[index] = slider;
|
||||
sliders[index] = slider;
|
||||
UIFactory.SetLayoutElement(sliderObj, minHeight: 25, minWidth: 70, flexibleWidth: 999, flexibleHeight: 0);
|
||||
slider.minValue = 0;
|
||||
slider.maxValue = 1;
|
@ -5,13 +5,19 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Widgets;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public class InteractiveDictionary : InteractiveValue, ICellPoolDataSource<CacheKeyValuePairCell>, ICacheObjectController
|
||||
{
|
||||
@ -168,7 +174,6 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
}
|
||||
|
||||
public int AdjustedWidth => (int)UIRect.rect.width - 80;
|
||||
//public int AdjustedKeyWidth => HalfWidth - 50;
|
||||
|
||||
public override void SetLayout()
|
||||
{
|
@ -5,9 +5,15 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public class InteractiveEnum : InteractiveValue
|
||||
{
|
||||
@ -18,10 +24,10 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
|
||||
public OrderedDictionary CurrentValues;
|
||||
|
||||
public CachedEnumValue ValueAtIdx(int idx) => (CachedEnumValue)CurrentValues[idx];
|
||||
public CachedEnumValue ValueAtKey(object key) => (CachedEnumValue)CurrentValues[key];
|
||||
private InputFieldRef inputField;
|
||||
private ButtonRef enumHelperButton;
|
||||
private EnumCompleter enumCompleter;
|
||||
|
||||
private Dropdown enumDropdown;
|
||||
private GameObject toggleHolder;
|
||||
private readonly List<Toggle> flagToggles = new List<Toggle>();
|
||||
private readonly List<Text> flagTexts = new List<Text>();
|
||||
@ -33,38 +39,68 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
|
||||
if (lastType != EnumType)
|
||||
{
|
||||
CurrentValues = GetEnumValues(EnumType, out IsFlags);
|
||||
CurrentValues = GetEnumValues(EnumType);
|
||||
|
||||
IsFlags = EnumType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] fa && fa.Any();
|
||||
if (IsFlags)
|
||||
SetupTogglesForEnumType();
|
||||
else
|
||||
SetupDropdownForEnumType();
|
||||
{
|
||||
inputField.Component.gameObject.SetActive(true);
|
||||
enumHelperButton.Component.gameObject.SetActive(true);
|
||||
toggleHolder.SetActive(false);
|
||||
}
|
||||
|
||||
enumCompleter.EnumType = EnumType;
|
||||
enumCompleter.CacheEnumValues();
|
||||
|
||||
lastType = EnumType;
|
||||
}
|
||||
|
||||
// setup ui for changes
|
||||
if (IsFlags)
|
||||
SetTogglesForValue(value);
|
||||
if (!IsFlags)
|
||||
inputField.Text = value.ToString();
|
||||
else
|
||||
SetDropdownForValue(value);
|
||||
SetTogglesForValue(value);
|
||||
|
||||
this.enumCompleter.chosenSuggestion = value.ToString();
|
||||
AutoCompleteModal.Instance.ReleaseOwnership(this.enumCompleter);
|
||||
}
|
||||
|
||||
private void SetTogglesForValue(object value)
|
||||
{
|
||||
try
|
||||
{
|
||||
var split = value.ToString().Split(',');
|
||||
var set = new HashSet<string>();
|
||||
foreach (var s in split)
|
||||
set.Add(s.Trim());
|
||||
|
||||
for (int i = 0; i < CurrentValues.Count; i++)
|
||||
flagToggles[i].isOn = set.Contains(ValueAtIdx(i).Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning("Exception setting flag toggles: " + ex);
|
||||
}
|
||||
}
|
||||
|
||||
// Setting value to owner
|
||||
|
||||
private void OnApplyClicked()
|
||||
{
|
||||
if (IsFlags)
|
||||
SetValueFromFlags();
|
||||
else
|
||||
SetValueFromDropdown();
|
||||
}
|
||||
|
||||
private void SetValueFromDropdown()
|
||||
{
|
||||
try
|
||||
{
|
||||
CurrentOwner.SetUserValue(ValueAtIdx(enumDropdown.value).ActualValue);
|
||||
if (!IsFlags)
|
||||
{
|
||||
if (ParseUtility.TryParse(this.inputField.Text, EnumType, out object value, out Exception ex))
|
||||
CurrentOwner.SetUserValue(value);
|
||||
else
|
||||
throw ex;
|
||||
}
|
||||
else
|
||||
{
|
||||
SetValueFromFlags();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@ -91,59 +127,53 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
}
|
||||
}
|
||||
|
||||
// setting UI state for value
|
||||
// UI Construction
|
||||
|
||||
private void SetDropdownForValue(object value)
|
||||
private void EnumHelper_OnClick()
|
||||
{
|
||||
if (CurrentValues.Contains(value))
|
||||
{
|
||||
var cached = ValueAtKey(value);
|
||||
enumDropdown.value = cached.EnumIndex;
|
||||
enumDropdown.RefreshShownValue();
|
||||
}
|
||||
else
|
||||
ExplorerCore.LogWarning("CurrentValues does not contain key '" + value?.ToString() ?? "<null>" + "'");
|
||||
enumCompleter.HelperButtonClicked();
|
||||
}
|
||||
|
||||
private void SetTogglesForValue(object value)
|
||||
public override GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
try
|
||||
{
|
||||
var split = value.ToString().Split(',');
|
||||
var set = new HashSet<string>();
|
||||
foreach (var s in split)
|
||||
set.Add(s.Trim());
|
||||
UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveEnum", false, false, true, true, 3, new Vector4(4, 4, 4, 4),
|
||||
new Color(0.06f, 0.06f, 0.06f));
|
||||
UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleHeight: 9999, flexibleWidth: 9999);
|
||||
|
||||
for (int i = 0; i < CurrentValues.Count; i++)
|
||||
flagToggles[i].isOn = set.Contains(ValueAtIdx(i).Name);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning("Exception setting flag toggles: " + ex);
|
||||
}
|
||||
var hori = UIFactory.CreateUIObject("Hori", UIRoot);
|
||||
UIFactory.SetLayoutElement(hori, minHeight: 25, flexibleWidth: 9999);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(hori, false, false, true, true, 2);
|
||||
|
||||
var applyButton = UIFactory.CreateButton(hori, "ApplyButton", "Apply", new Color(0.2f, 0.27f, 0.2f));
|
||||
UIFactory.SetLayoutElement(applyButton.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
applyButton.OnClick += OnApplyClicked;
|
||||
|
||||
inputField = UIFactory.CreateInputField(hori, "InputField", "Enter name or underlying value...");
|
||||
UIFactory.SetLayoutElement(inputField.UIRoot, minHeight: 25, flexibleHeight: 50, minWidth: 100, flexibleWidth: 1000);
|
||||
inputField.Component.lineType = InputField.LineType.MultiLineNewline;
|
||||
inputField.UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
enumHelperButton = UIFactory.CreateButton(hori, "EnumHelper", "▼");
|
||||
UIFactory.SetLayoutElement(enumHelperButton.Component.gameObject, minWidth: 25, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0);
|
||||
enumHelperButton.OnClick += EnumHelper_OnClick;
|
||||
|
||||
enumCompleter = new EnumCompleter(this.EnumType, this.inputField);
|
||||
|
||||
toggleHolder = UIFactory.CreateUIObject("ToggleHolder", UIRoot);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(toggleHolder, false, false, true, true, 4);
|
||||
UIFactory.SetLayoutElement(toggleHolder, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
||||
// Setting up the UI for the enum type when it changes or is first set
|
||||
|
||||
private void SetupDropdownForEnumType()
|
||||
{
|
||||
toggleHolder.SetActive(false);
|
||||
enumDropdown.gameObject.SetActive(true);
|
||||
|
||||
// create dropdown entries
|
||||
enumDropdown.options.Clear();
|
||||
|
||||
foreach (CachedEnumValue entry in CurrentValues.Values)
|
||||
enumDropdown.options.Add(new Dropdown.OptionData(entry.Name));
|
||||
|
||||
enumDropdown.value = 0;
|
||||
enumDropdown.RefreshShownValue();
|
||||
}
|
||||
public CachedEnumValue ValueAtIdx(int idx) => (CachedEnumValue)CurrentValues[idx];
|
||||
public CachedEnumValue ValueAtKey(object key) => (CachedEnumValue)CurrentValues[key];
|
||||
|
||||
private void SetupTogglesForEnumType()
|
||||
{
|
||||
toggleHolder.SetActive(true);
|
||||
enumDropdown.gameObject.SetActive(false);
|
||||
inputField.Component.gameObject.SetActive(false);
|
||||
enumHelperButton.Component.gameObject.SetActive(false);
|
||||
|
||||
// create / set / hide toggles
|
||||
for (int i = 0; i < CurrentValues.Count || i < flagToggles.Count; i++)
|
||||
@ -178,54 +208,13 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
flagTexts.Add(toggleText);
|
||||
}
|
||||
|
||||
// UI Construction
|
||||
|
||||
public override GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveEnum", false, false, true, true, 3, new Vector4(4, 4, 4, 4),
|
||||
new Color(0.06f, 0.06f, 0.06f));
|
||||
UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleHeight: 9999, flexibleWidth: 9999);
|
||||
|
||||
var hori = UIFactory.CreateUIObject("Hori", UIRoot);
|
||||
UIFactory.SetLayoutElement(hori, minHeight: 25, flexibleWidth: 9999);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(hori, false, false, true, true, 2);
|
||||
|
||||
var applyButton = UIFactory.CreateButton(hori, "ApplyButton", "Apply", new Color(0.2f, 0.27f, 0.2f));
|
||||
UIFactory.SetLayoutElement(applyButton.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
applyButton.OnClick += OnApplyClicked;
|
||||
|
||||
var dropdownObj = UIFactory.CreateDropdown(hori, out enumDropdown, "not set", 14, null);
|
||||
UIFactory.SetLayoutElement(dropdownObj, minHeight: 25, flexibleWidth: 600);
|
||||
|
||||
toggleHolder = UIFactory.CreateUIObject("ToggleHolder", UIRoot);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(toggleHolder, false, false, true, true, 4);
|
||||
UIFactory.SetLayoutElement(toggleHolder, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
||||
|
||||
#region Enum cache
|
||||
|
||||
public struct CachedEnumValue
|
||||
{
|
||||
public CachedEnumValue(object value, int index, string name)
|
||||
{
|
||||
EnumIndex = index;
|
||||
Name = name;
|
||||
ActualValue = value;
|
||||
}
|
||||
|
||||
public readonly object ActualValue;
|
||||
public int EnumIndex;
|
||||
public readonly string Name;
|
||||
}
|
||||
|
||||
internal static readonly Dictionary<string, OrderedDictionary> enumCache = new Dictionary<string, OrderedDictionary>();
|
||||
|
||||
internal static OrderedDictionary GetEnumValues(Type enumType, out bool isFlags)
|
||||
internal static OrderedDictionary GetEnumValues(Type enumType)
|
||||
{
|
||||
isFlags = enumType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] fa && fa.Any();
|
||||
//isFlags = enumType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] fa && fa.Any();
|
||||
|
||||
if (!enumCache.ContainsKey(enumType.AssemblyQualifiedName))
|
||||
{
|
||||
@ -252,4 +241,18 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public struct CachedEnumValue
|
||||
{
|
||||
public CachedEnumValue(object value, int index, string name)
|
||||
{
|
||||
EnumIndex = index;
|
||||
Name = name;
|
||||
ActualValue = value;
|
||||
}
|
||||
|
||||
public readonly object ActualValue;
|
||||
public int EnumIndex;
|
||||
public readonly string Name;
|
||||
}
|
||||
}
|
@ -5,13 +5,19 @@ using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Widgets;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public class InteractiveList : InteractiveValue, ICellPoolDataSource<CacheListEntryCell>, ICacheObjectController
|
||||
{
|
||||
@ -28,11 +34,13 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
private PropertyInfo genericIndexer;
|
||||
|
||||
public int ItemCount => cachedEntries.Count;
|
||||
private readonly List<CacheListEntry> cachedEntries = new List<CacheListEntry>();
|
||||
private readonly List<CacheListEntry> cachedEntries = new();
|
||||
|
||||
public ScrollPool<CacheListEntryCell> ListScrollPool { get; private set; }
|
||||
|
||||
public Text TopLabel;
|
||||
private LayoutElement scrollLayout;
|
||||
private Text NotSupportedLabel;
|
||||
|
||||
public override void OnBorrowed(CacheObjectBase owner)
|
||||
{
|
||||
@ -61,6 +69,28 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
cachedEntries.Clear();
|
||||
}
|
||||
|
||||
// List entry scroll pool
|
||||
|
||||
public override void SetLayout()
|
||||
{
|
||||
var minHeight = 5f;
|
||||
|
||||
foreach (var cell in ListScrollPool.CellPool)
|
||||
{
|
||||
if (cell.Enabled)
|
||||
minHeight += cell.Rect.rect.height;
|
||||
}
|
||||
|
||||
this.scrollLayout.minHeight = Math.Min(InspectorPanel.CurrentPanelHeight - 400f, minHeight);
|
||||
}
|
||||
|
||||
public void OnCellBorrowed(CacheListEntryCell cell) { } // not needed
|
||||
|
||||
public void SetCell(CacheListEntryCell cell, int index)
|
||||
{
|
||||
CacheObjectControllerHelper.SetCell(cell, index, cachedEntries, null);
|
||||
}
|
||||
|
||||
// Setting the List value itself to this model
|
||||
public override void SetValue(object value)
|
||||
{
|
||||
@ -208,32 +238,6 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
}
|
||||
}
|
||||
|
||||
// List entry scroll pool
|
||||
|
||||
public override void SetLayout()
|
||||
{
|
||||
var minHeight = 5f;
|
||||
|
||||
foreach (var cell in ListScrollPool.CellPool)
|
||||
{
|
||||
if (cell.Enabled)
|
||||
minHeight += cell.Rect.rect.height;
|
||||
}
|
||||
|
||||
this.scrollLayout.minHeight = Math.Min(InspectorPanel.CurrentPanelHeight - 400f, minHeight);
|
||||
}
|
||||
|
||||
public void OnCellBorrowed(CacheListEntryCell cell) { } // not needed
|
||||
|
||||
public void SetCell(CacheListEntryCell cell, int index)
|
||||
{
|
||||
CacheObjectControllerHelper.SetCell(cell, index, cachedEntries, null);
|
||||
}
|
||||
|
||||
private LayoutElement scrollLayout;
|
||||
|
||||
private Text NotSupportedLabel;
|
||||
|
||||
public override GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveList", true, true, true, true, 6, new Vector4(10, 3, 15, 4),
|
@ -5,11 +5,16 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.Config;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public class InteractiveString : InteractiveValue
|
||||
{
|
||||
@ -37,7 +42,7 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
if (s == null)
|
||||
return false;
|
||||
|
||||
return s.Length >= UIManager.MAX_INPUTFIELD_CHARS;
|
||||
return s.Length >= UniversalUI.MAX_INPUTFIELD_CHARS;
|
||||
}
|
||||
|
||||
public override void SetValue(object value)
|
||||
@ -79,7 +84,7 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
return;
|
||||
}
|
||||
|
||||
var path = IOUtility.EnsureValidDirectory(SaveFilePath.Text);
|
||||
var path = IOUtility.EnsureValidFilePath(SaveFilePath.Text);
|
||||
|
||||
if (File.Exists(path))
|
||||
File.Delete(path);
|
@ -4,10 +4,13 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.UI;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.ObjectPool;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public abstract class InteractiveValue : IPooledObject
|
||||
{
|
@ -5,9 +5,14 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.UI;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public class InteractiveValueStruct : InteractiveValue
|
||||
{
|
||||
@ -52,7 +57,7 @@ namespace UnityExplorer.UI.CacheObject.IValues
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, StructInfo> typeSupportCache = new Dictionary<string, StructInfo>();
|
||||
private static readonly Dictionary<string, StructInfo> typeSupportCache = new();
|
||||
|
||||
private const BindingFlags INSTANCE_FLAGS = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
|
||||
private const string SYSTEM_VOID = "System.Void";
|
@ -4,8 +4,12 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.Views
|
||||
namespace UnityExplorer.CacheObject.Views
|
||||
{
|
||||
public class ConfigEntryCell : CacheObjectCell
|
||||
{
|
@ -4,11 +4,14 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject.IValues;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.CacheObject.IValues;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.Views
|
||||
namespace UnityExplorer.CacheObject.Views
|
||||
{
|
||||
public class CacheKeyValuePairCell : CacheObjectCell
|
||||
{
|
@ -4,9 +4,9 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject.IValues;
|
||||
using UnityExplorer.CacheObject.IValues;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.Views
|
||||
namespace UnityExplorer.CacheObject.Views
|
||||
{
|
||||
public class CacheListEntryCell : CacheObjectCell
|
||||
{
|
@ -4,21 +4,20 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.Views
|
||||
namespace UnityExplorer.CacheObject.Views
|
||||
{
|
||||
public class CacheMemberCell : CacheObjectCell
|
||||
{
|
||||
//public ReflectionInspector Owner { get; set; }
|
||||
|
||||
public CacheMember MemberOccupant => Occupant as CacheMember;
|
||||
|
||||
public GameObject EvaluateHolder;
|
||||
public ButtonRef EvaluateButton;
|
||||
|
||||
//public Toggle UpdateToggle;
|
||||
|
||||
protected virtual void EvaluateClicked()
|
||||
{
|
||||
this.MemberOccupant.OnEvaluateClicked();
|
||||
@ -36,16 +35,5 @@ namespace UnityExplorer.UI.CacheObject.Views
|
||||
UIFactory.SetLayoutElement(EvaluateButton.Component.gameObject, minWidth: 100, minHeight: 25);
|
||||
EvaluateButton.OnClick += EvaluateClicked;
|
||||
}
|
||||
|
||||
//protected override void ConstructUpdateToggle(GameObject parent)
|
||||
//{
|
||||
// // Auto-update toggle
|
||||
//
|
||||
// var updateToggle = UIFactory.CreateToggle(parent, "AutoUpdate", out UpdateToggle, out Text autoText);
|
||||
// UIFactory.SetLayoutElement(updateToggle, minHeight: 25, minWidth: 30, flexibleWidth: 0, flexibleHeight: 0);
|
||||
// GameObject.Destroy(autoText);
|
||||
// UpdateToggle.isOn = false;
|
||||
// UpdateToggle.onValueChanged.AddListener((bool val) => { MemberOccupant.AutoUpdateWanted = val; });
|
||||
//}
|
||||
}
|
||||
}
|
@ -4,11 +4,14 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject.IValues;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.Views
|
||||
namespace UnityExplorer.CacheObject.Views
|
||||
{
|
||||
public abstract class CacheObjectCell : ICell
|
||||
{
|
||||
@ -41,9 +44,12 @@ namespace UnityExplorer.UI.CacheObject.Views
|
||||
public bool SubContentActive => SubContentHolder.activeSelf;
|
||||
|
||||
public LayoutElement NameLayout;
|
||||
public GameObject RightGroupContent;
|
||||
public LayoutElement RightGroupLayout;
|
||||
public GameObject SubContentHolder;
|
||||
|
||||
public Text NameLabel;
|
||||
public InputFieldRef HiddenNameLabel; // for selecting the name label
|
||||
public Text TypeLabel;
|
||||
public Text ValueLabel;
|
||||
public Toggle Toggle;
|
||||
@ -54,7 +60,11 @@ namespace UnityExplorer.UI.CacheObject.Views
|
||||
public ButtonRef SubContentButton;
|
||||
public ButtonRef ApplyButton;
|
||||
|
||||
public GameObject SubContentHolder;
|
||||
public ButtonRef CopyButton;
|
||||
public ButtonRef PasteButton;
|
||||
|
||||
public readonly Color subInactiveColor = new(0.23f, 0.23f, 0.23f);
|
||||
public readonly Color subActiveColor = new(0.23f, 0.33f, 0.23f);
|
||||
|
||||
protected virtual void ApplyClicked()
|
||||
{
|
||||
@ -76,26 +86,26 @@ namespace UnityExplorer.UI.CacheObject.Views
|
||||
this.Occupant.OnCellSubContentToggle();
|
||||
}
|
||||
|
||||
public readonly Color subInactiveColor = new Color(0.23f, 0.23f, 0.23f);
|
||||
public readonly Color subActiveColor = new Color(0.23f, 0.33f, 0.23f);
|
||||
protected virtual void OnCopyClicked()
|
||||
{
|
||||
ClipboardPanel.Copy(this.Occupant.Value);
|
||||
}
|
||||
|
||||
protected virtual void OnPasteClicked()
|
||||
{
|
||||
if (ClipboardPanel.TryPaste(this.Occupant.FallbackType, out object paste))
|
||||
this.Occupant.SetUserValue(paste);
|
||||
}
|
||||
|
||||
public void RefreshSubcontentButton()
|
||||
{
|
||||
if (!this.SubContentHolder.activeSelf)
|
||||
{
|
||||
this.SubContentButton.ButtonText.text = "▲";
|
||||
RuntimeProvider.Instance.SetColorBlock(SubContentButton.Component, subInactiveColor, subInactiveColor * 1.3f);
|
||||
}
|
||||
else
|
||||
{
|
||||
this.SubContentButton.ButtonText.text = "▼";
|
||||
RuntimeProvider.Instance.SetColorBlock(SubContentButton.Component, subActiveColor, subActiveColor * 1.3f);
|
||||
}
|
||||
this.SubContentButton.ButtonText.text = SubContentHolder.activeSelf ? "▼" : "▲";
|
||||
Color color = SubContentHolder.activeSelf ? subActiveColor : subInactiveColor;
|
||||
RuntimeHelper.SetColorBlock(SubContentButton.Component, color, color * 1.3f);
|
||||
}
|
||||
|
||||
protected abstract void ConstructEvaluateHolder(GameObject parent);
|
||||
|
||||
|
||||
public virtual GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
// Main layout
|
||||
@ -115,21 +125,32 @@ namespace UnityExplorer.UI.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
|
||||
|
||||
var rightGroupHolder = UIFactory.CreateUIObject("RightGroup", horiRow);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(rightGroupHolder, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft);
|
||||
UIFactory.SetLayoutElement(rightGroupHolder, minHeight: 25, minWidth: 200, flexibleWidth: 9999, flexibleHeight: 800);
|
||||
RightGroupLayout = rightGroupHolder.GetComponent<LayoutElement>();
|
||||
RightGroupContent = UIFactory.CreateUIObject("RightGroup", horiRow);
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(RightGroupContent, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft);
|
||||
UIFactory.SetLayoutElement(RightGroupContent, minHeight: 25, minWidth: 200, flexibleWidth: 9999, flexibleHeight: 800);
|
||||
RightGroupLayout = RightGroupContent.GetComponent<LayoutElement>();
|
||||
|
||||
ConstructEvaluateHolder(rightGroupHolder);
|
||||
ConstructEvaluateHolder(RightGroupContent);
|
||||
|
||||
// Right horizontal group
|
||||
|
||||
var rightHoriGroup = UIFactory.CreateUIObject("RightHoriGroup", rightGroupHolder);
|
||||
var rightHoriGroup = UIFactory.CreateUIObject("RightHoriGroup", RightGroupContent);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rightHoriGroup, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft);
|
||||
UIFactory.SetLayoutElement(rightHoriGroup, minHeight: 25, minWidth: 200, flexibleWidth: 9999, flexibleHeight: 800);
|
||||
|
||||
@ -141,7 +162,7 @@ namespace UnityExplorer.UI.CacheObject.Views
|
||||
|
||||
TypeLabel = UIFactory.CreateLabel(rightHoriGroup, "ReturnLabel", "<notset>", TextAnchor.MiddleLeft);
|
||||
TypeLabel.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
UIFactory.SetLayoutElement(TypeLabel.gameObject, minHeight: 25, flexibleHeight: 150, minWidth: 60, flexibleWidth: 0);
|
||||
UIFactory.SetLayoutElement(TypeLabel.gameObject, minHeight: 25, flexibleHeight: 150, minWidth: 45, flexibleWidth: 0);
|
||||
|
||||
// Bool and number value interaction
|
||||
|
||||
@ -171,6 +192,24 @@ namespace UnityExplorer.UI.CacheObject.Views
|
||||
ValueLabel.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
UIFactory.SetLayoutElement(ValueLabel.gameObject, minHeight: 25, flexibleHeight: 150, flexibleWidth: 9999);
|
||||
|
||||
// Copy and Paste buttons
|
||||
|
||||
var buttonHolder = UIFactory.CreateHorizontalGroup(rightHoriGroup, "CopyPasteButtons", false, false, true, true, 4,
|
||||
bgColor: new(1,1,1,0), childAlignment: TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(buttonHolder, minWidth: 60, flexibleWidth: 0);
|
||||
|
||||
CopyButton = UIFactory.CreateButton(buttonHolder, "CopyButton", "Copy", new Color(0.13f, 0.13f, 0.13f, 1f));
|
||||
UIFactory.SetLayoutElement(CopyButton.Component.gameObject, minHeight: 25, minWidth: 28, flexibleWidth: 0);
|
||||
CopyButton.ButtonText.color = Color.yellow;
|
||||
CopyButton.ButtonText.fontSize = 10;
|
||||
CopyButton.OnClick += OnCopyClicked;
|
||||
|
||||
PasteButton = UIFactory.CreateButton(buttonHolder, "PasteButton", "Paste", new Color(0.13f, 0.13f, 0.13f, 1f));
|
||||
UIFactory.SetLayoutElement(PasteButton.Component.gameObject, minHeight: 25, minWidth: 28, flexibleWidth: 0);
|
||||
PasteButton.ButtonText.color = Color.green;
|
||||
PasteButton.ButtonText.fontSize = 10;
|
||||
PasteButton.OnClick += OnPasteClicked;
|
||||
|
||||
// Subcontent
|
||||
|
||||
SubContentHolder = UIFactory.CreateUIObject("SubContent", UIRoot);
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace UnityExplorer.Core.Config
|
||||
namespace UnityExplorer.Config
|
||||
{
|
||||
public class ConfigElement<T> : IConfigElement
|
||||
{
|
@ -3,7 +3,7 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace UnityExplorer.Core.Config
|
||||
namespace UnityExplorer.Config
|
||||
{
|
||||
public abstract class ConfigHandler
|
||||
{
|
@ -8,15 +8,20 @@ using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Core.Config
|
||||
namespace UnityExplorer.Config
|
||||
{
|
||||
public static class ConfigManager
|
||||
{
|
||||
internal static readonly Dictionary<string, IConfigElement> ConfigElements = new();
|
||||
internal static readonly Dictionary<string, IConfigElement> InternalConfigs = new();
|
||||
|
||||
// Each Mod Loader has its own ConfigHandler.
|
||||
// See the UnityExplorer.Loader namespace for the implementations.
|
||||
public static ConfigHandler Handler { get; private set; }
|
||||
|
||||
// Actual UE Settings
|
||||
public static ConfigElement<KeyCode> Master_Toggle;
|
||||
public static ConfigElement<int> Target_Display;
|
||||
public static ConfigElement<UIManager.VerticalAnchor> Main_Navbar_Anchor;
|
||||
public static ConfigElement<bool> Force_Unlock_Mouse;
|
||||
public static ConfigElement<KeyCode> Force_Unlock_Toggle;
|
||||
@ -26,20 +31,18 @@ namespace UnityExplorer.Core.Config
|
||||
public static ConfigElement<bool> Log_Unity_Debug;
|
||||
public static ConfigElement<bool> Hide_On_Startup;
|
||||
public static ConfigElement<float> Startup_Delay_Time;
|
||||
|
||||
public static ConfigElement<string> Reflection_Signature_Blacklist;
|
||||
|
||||
// internal configs
|
||||
internal static InternalConfigHandler InternalHandler { get; private set; }
|
||||
internal static readonly Dictionary<UIManager.Panels, ConfigElement<string>> PanelSaveData = new();
|
||||
|
||||
public static ConfigElement<string> ObjectExplorerData;
|
||||
public static ConfigElement<string> InspectorData;
|
||||
public static ConfigElement<string> CSConsoleData;
|
||||
public static ConfigElement<string> OptionsPanelData;
|
||||
public static ConfigElement<string> ConsoleLogData;
|
||||
|
||||
internal static readonly Dictionary<string, IConfigElement> ConfigElements = new Dictionary<string, IConfigElement>();
|
||||
internal static readonly Dictionary<string, IConfigElement> InternalConfigs = new Dictionary<string, IConfigElement>();
|
||||
internal static ConfigElement<string> GetPanelSaveData(UIManager.Panels panel)
|
||||
{
|
||||
if (!PanelSaveData.ContainsKey(panel))
|
||||
PanelSaveData.Add(panel, new ConfigElement<string>(panel.ToString(), string.Empty, string.Empty, true));
|
||||
return PanelSaveData[panel];
|
||||
}
|
||||
|
||||
public static void Init(ConfigHandler configHandler)
|
||||
{
|
||||
@ -77,6 +80,11 @@ namespace UnityExplorer.Core.Config
|
||||
"The key to enable or disable UnityExplorer's menu and features.",
|
||||
KeyCode.F7);
|
||||
|
||||
Target_Display = new ConfigElement<int>("Target Display",
|
||||
"The monitor index for UnityExplorer to use, if you have multiple. 0 is the default display, 1 is secondary, etc. " +
|
||||
"Restart recommended when changing this setting. Make sure your extra monitors are the same resolution as your primary monitor.",
|
||||
0);
|
||||
|
||||
Main_Navbar_Anchor = new ConfigElement<UIManager.VerticalAnchor>("Main Navbar Anchor",
|
||||
"The vertical anchor of the main UnityExplorer Navbar, in case you want to move it.",
|
||||
UIManager.VerticalAnchor.Top);
|
||||
@ -88,18 +96,22 @@ namespace UnityExplorer.Core.Config
|
||||
Force_Unlock_Mouse = new ConfigElement<bool>("Force Unlock Mouse",
|
||||
"Force the Cursor to be unlocked (visible) when the UnityExplorer menu is open.",
|
||||
true);
|
||||
Force_Unlock_Mouse.OnValueChanged += (bool value) =>
|
||||
{
|
||||
UniverseLib.Config.ConfigManager.Force_Unlock_Mouse = value;
|
||||
};
|
||||
|
||||
Force_Unlock_Toggle = new ConfigElement<KeyCode>("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<bool>("Aggressive Mouse Unlock",
|
||||
"Use WaitForEndOfFrame to aggressively force the Mouse to be unlocked.\n<b>Requires restart to take effect.</b>",
|
||||
false);
|
||||
|
||||
Disable_EventSystem_Override = new ConfigElement<bool>("Disable EventSystem override",
|
||||
"If enabled, UnityExplorer will not override the EventSystem from the game.\n<b>May require restart to take effect.</b>",
|
||||
false);
|
||||
Disable_EventSystem_Override.OnValueChanged += (bool value) =>
|
||||
{
|
||||
UniverseLib.Config.ConfigManager.Disable_EventSystem_Override = value;
|
||||
};
|
||||
|
||||
Log_Unity_Debug = new ConfigElement<bool>("Log Unity Debug",
|
||||
"Should UnityEngine.Debug.Log messages be printed to UnityExplorer's log?",
|
||||
@ -116,16 +128,8 @@ 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)
|
||||
|
||||
ObjectExplorerData = new ConfigElement<string>("ObjectExplorer", "", "", true);
|
||||
InspectorData = new ConfigElement<string>("Inspector", "", "", true);
|
||||
CSConsoleData = new ConfigElement<string>("CSConsole", "", "", true);
|
||||
OptionsPanelData = new ConfigElement<string>("OptionsPanel", "", "", true);
|
||||
ConsoleLogData = new ConfigElement<string>("ConsoleLog", "", "", true);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace UnityExplorer.Core.Config
|
||||
namespace UnityExplorer.Config
|
||||
{
|
||||
public interface IConfigElement
|
||||
{
|
79
src/Config/InternalConfigHandler.cs
Normal file
79
src/Config/InternalConfigHandler.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.UI;
|
||||
using Tomlet;
|
||||
using Tomlet.Models;
|
||||
|
||||
namespace UnityExplorer.Config
|
||||
{
|
||||
public class InternalConfigHandler : ConfigHandler
|
||||
{
|
||||
internal static string CONFIG_PATH;
|
||||
|
||||
public override void Init()
|
||||
{
|
||||
CONFIG_PATH = Path.Combine(ExplorerCore.Loader.ExplorerFolder, "data.cfg");
|
||||
}
|
||||
|
||||
public override void LoadConfig()
|
||||
{
|
||||
if (!TryLoadConfig())
|
||||
SaveConfig();
|
||||
}
|
||||
|
||||
public override void RegisterConfigElement<T>(ConfigElement<T> element)
|
||||
{
|
||||
// Not necessary
|
||||
}
|
||||
|
||||
public override void SetConfigValue<T>(ConfigElement<T> element, T value)
|
||||
{
|
||||
// Not necessary
|
||||
}
|
||||
|
||||
// Not necessary, just return the value.
|
||||
public override T GetConfigValue<T>(ConfigElement<T> element) => element.Value;
|
||||
|
||||
// Always just auto-save.
|
||||
public override void OnAnyConfigChanged() => SaveConfig();
|
||||
|
||||
public bool TryLoadConfig()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!File.Exists(CONFIG_PATH))
|
||||
return false;
|
||||
|
||||
TomlDocument document = TomlParser.ParseFile(CONFIG_PATH);
|
||||
foreach (var key in document.Keys)
|
||||
{
|
||||
var panelKey = (UIManager.Panels)Enum.Parse(typeof(UIManager.Panels), key);
|
||||
ConfigManager.GetPanelSaveData(panelKey).Value = document.GetString(key);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning("Error loading internal data: " + ex.ToString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public override void SaveConfig()
|
||||
{
|
||||
if (UIManager.Initializing)
|
||||
return;
|
||||
|
||||
var tomlDocument = TomlDocument.CreateEmpty();
|
||||
foreach (var entry in ConfigManager.InternalConfigs)
|
||||
tomlDocument.Put(entry.Key, entry.Value.BoxedValue as string, false);
|
||||
|
||||
File.WriteAllText(CONFIG_PATH, tomlDocument.SerializedValue);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,105 +0,0 @@
|
||||
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<T>(ConfigElement<T> element)
|
||||
{
|
||||
// Not necessary
|
||||
}
|
||||
|
||||
public override void SetConfigValue<T>(ConfigElement<T> element, T value)
|
||||
{
|
||||
// Not necessary
|
||||
}
|
||||
|
||||
public override T GetConfigValue<T>(ConfigElement<T> 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());
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
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<ExplorerBehaviour>();
|
||||
#endif
|
||||
|
||||
var obj = new GameObject("ExplorerBehaviour");
|
||||
GameObject.DontDestroyOnLoad(obj);
|
||||
obj.hideFlags |= HideFlags.HideAndDontSave;
|
||||
Instance = obj.AddComponent<ExplorerBehaviour>();
|
||||
}
|
||||
|
||||
#if CPP
|
||||
public ExplorerBehaviour(IntPtr ptr) : base(ptr) { }
|
||||
#endif
|
||||
|
||||
private static bool onPostRenderFailed;
|
||||
|
||||
internal void Awake()
|
||||
{
|
||||
try
|
||||
{
|
||||
#if CPP
|
||||
Camera.onPostRender = Camera.onPostRender == null
|
||||
? new Action<Camera>(OnPostRender)
|
||||
: Il2CppSystem.Delegate.Combine(Camera.onPostRender,
|
||||
(Camera.CameraCallback)new Action<Camera>(OnPostRender)).Cast<Camera.CameraCallback>();
|
||||
|
||||
if (Camera.onPostRender == null || Camera.onPostRender.delegates == null)
|
||||
{
|
||||
ExplorerCore.LogWarning("Failed to add Camera.onPostRender listener, falling back to LateUpdate instead!");
|
||||
onPostRenderFailed = true;
|
||||
}
|
||||
#else
|
||||
Camera.onPostRender += OnPostRender;
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception adding onPostRender listener: {ex.ReflectionExToString()}\r\nFalling back to LateUpdate!");
|
||||
onPostRenderFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
ExplorerCore.Update();
|
||||
}
|
||||
|
||||
internal void FixedUpdate()
|
||||
{
|
||||
ExplorerCore.FixedUpdate();
|
||||
}
|
||||
|
||||
internal void LateUpdate()
|
||||
{
|
||||
if (onPostRenderFailed)
|
||||
OnPostRender(null);
|
||||
}
|
||||
|
||||
internal static void OnPostRender(Camera _)
|
||||
{
|
||||
ExplorerCore.OnPostRender();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,208 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.UI;
|
||||
using BF = System.Reflection.BindingFlags;
|
||||
|
||||
|
||||
namespace UnityExplorer.Core.Input
|
||||
{
|
||||
public class CursorUnlocker
|
||||
{
|
||||
public static bool Unlock
|
||||
{
|
||||
get => m_forceUnlock;
|
||||
set
|
||||
{
|
||||
m_forceUnlock = value;
|
||||
UpdateCursorControl();
|
||||
}
|
||||
}
|
||||
private static bool m_forceUnlock;
|
||||
|
||||
public static bool ShouldActuallyUnlock => UIManager.ShowMenu && Unlock;
|
||||
|
||||
private static CursorLockMode lastLockMode;
|
||||
private static bool lastVisibleState;
|
||||
|
||||
private static bool currentlySettingCursor = false;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
lastLockMode = Cursor.lockState;
|
||||
lastVisibleState = Cursor.visible;
|
||||
|
||||
SetupPatches();
|
||||
UpdateCursorControl();
|
||||
|
||||
// Hook up config values
|
||||
|
||||
// Force Unlock Mouse
|
||||
Unlock = ConfigManager.Force_Unlock_Mouse.Value;
|
||||
ConfigManager.Force_Unlock_Mouse.OnValueChanged += (bool val) => { Unlock = val; };
|
||||
|
||||
// Aggressive Mouse Unlock
|
||||
if (ConfigManager.Aggressive_Mouse_Unlock.Value)
|
||||
SetupAggressiveUnlock();
|
||||
}
|
||||
|
||||
public static void SetupAggressiveUnlock()
|
||||
{
|
||||
try
|
||||
{
|
||||
RuntimeProvider.Instance.StartCoroutine(AggressiveUnlockCoroutine());
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception setting up Aggressive Mouse Unlock: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private static WaitForEndOfFrame _waitForEndOfFrame = new WaitForEndOfFrame();
|
||||
|
||||
private static IEnumerator AggressiveUnlockCoroutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return _waitForEndOfFrame ?? (_waitForEndOfFrame = new WaitForEndOfFrame());
|
||||
|
||||
if (UIManager.ShowMenu)
|
||||
UpdateCursorControl();
|
||||
}
|
||||
}
|
||||
|
||||
public static void UpdateCursorControl()
|
||||
{
|
||||
try
|
||||
{
|
||||
currentlySettingCursor = true;
|
||||
|
||||
if (ShouldActuallyUnlock)
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
Cursor.visible = true;
|
||||
|
||||
if (!ConfigManager.Disable_EventSystem_Override.Value && UIManager.EventSys)
|
||||
SetEventSystem();
|
||||
}
|
||||
else
|
||||
{
|
||||
Cursor.lockState = lastLockMode;
|
||||
Cursor.visible = lastVisibleState;
|
||||
|
||||
if (!ConfigManager.Disable_EventSystem_Override.Value && UIManager.EventSys)
|
||||
ReleaseEventSystem();
|
||||
}
|
||||
|
||||
currentlySettingCursor = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log($"Exception setting Cursor state: {e.GetType()}, {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Event system overrides
|
||||
|
||||
private static bool settingEventSystem;
|
||||
private static EventSystem lastEventSystem;
|
||||
private static BaseInputModule lastInputModule;
|
||||
|
||||
public static void SetEventSystem()
|
||||
{
|
||||
if (InputManager.CurrentType == InputType.InputSystem)
|
||||
return;
|
||||
|
||||
if (EventSystem.current && EventSystem.current != UIManager.EventSys)
|
||||
{
|
||||
lastEventSystem = EventSystem.current;
|
||||
lastEventSystem.enabled = false;
|
||||
}
|
||||
|
||||
// Set to our current system
|
||||
settingEventSystem = true;
|
||||
UIManager.EventSys.enabled = true;
|
||||
EventSystem.current = UIManager.EventSys;
|
||||
InputManager.ActivateUIModule();
|
||||
settingEventSystem = false;
|
||||
}
|
||||
|
||||
public static void ReleaseEventSystem()
|
||||
{
|
||||
if (InputManager.CurrentType == InputType.InputSystem)
|
||||
return;
|
||||
|
||||
if (lastEventSystem && lastEventSystem.gameObject.activeSelf)
|
||||
{
|
||||
lastEventSystem.enabled = true;
|
||||
|
||||
settingEventSystem = true;
|
||||
EventSystem.current = lastEventSystem;
|
||||
lastInputModule?.ActivateModule();
|
||||
settingEventSystem = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Patches
|
||||
|
||||
private static void SetupPatches()
|
||||
{
|
||||
try
|
||||
{
|
||||
ExplorerCore.Loader.SetupCursorPatches();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log($"Exception setting up Cursor patches: {e.GetType()}, {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
public static void Prefix_EventSystem_set_current(ref EventSystem value)
|
||||
{
|
||||
if (!settingEventSystem && value)
|
||||
{
|
||||
lastEventSystem = value;
|
||||
lastInputModule = value.currentInputModule;
|
||||
}
|
||||
|
||||
if (!UIManager.EventSys)
|
||||
return;
|
||||
|
||||
if (!settingEventSystem && ShouldActuallyUnlock && !ConfigManager.Disable_EventSystem_Override.Value)
|
||||
{
|
||||
value = UIManager.EventSys;
|
||||
value.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
|
||||
// Also keep track of when anything else tries to set Cursor state, this will be the
|
||||
// value that we set back to when we close the menu or disable force-unlock.
|
||||
|
||||
public static void Prefix_set_lockState(ref CursorLockMode value)
|
||||
{
|
||||
if (!currentlySettingCursor)
|
||||
{
|
||||
lastLockMode = value;
|
||||
|
||||
if (ShouldActuallyUnlock)
|
||||
value = CursorLockMode.None;
|
||||
}
|
||||
}
|
||||
|
||||
public static void Prefix_set_visible(ref bool value)
|
||||
{
|
||||
if (!currentlySettingCursor)
|
||||
{
|
||||
lastVisibleState = value;
|
||||
|
||||
if (ShouldActuallyUnlock)
|
||||
value = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,22 +0,0 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UnityExplorer.Core.Input
|
||||
{
|
||||
public interface IHandleInput
|
||||
{
|
||||
Vector2 MousePosition { get; }
|
||||
Vector2 MouseScrollDelta { get; }
|
||||
|
||||
bool GetKeyDown(KeyCode key);
|
||||
bool GetKey(KeyCode key);
|
||||
|
||||
bool GetMouseButtonDown(int btn);
|
||||
bool GetMouseButton(int btn);
|
||||
|
||||
BaseInputModule UIModule { get; }
|
||||
|
||||
void AddUIInputModule();
|
||||
void ActivateModule();
|
||||
}
|
||||
}
|
@ -1,103 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UnityExplorer.Core.Input
|
||||
{
|
||||
public enum InputType
|
||||
{
|
||||
InputSystem,
|
||||
Legacy,
|
||||
None
|
||||
}
|
||||
|
||||
public static class InputManager
|
||||
{
|
||||
public static InputType CurrentType { get; private set; }
|
||||
|
||||
private static IHandleInput m_inputModule;
|
||||
|
||||
public static Vector3 MousePosition => m_inputModule.MousePosition;
|
||||
|
||||
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()
|
||||
{
|
||||
m_inputModule.AddUIInputModule();
|
||||
ActivateUIModule();
|
||||
}
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Core.Input
|
||||
{
|
||||
public class InputSystem : IHandleInput
|
||||
{
|
||||
public InputSystem()
|
||||
{
|
||||
m_kbCurrentProp = TKeyboard.GetProperty("current");
|
||||
m_kbIndexer = TKeyboard.GetProperty("Item", new Type[] { TKey });
|
||||
|
||||
var btnControl = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Controls.ButtonControl");
|
||||
m_btnIsPressedProp = btnControl.GetProperty("isPressed");
|
||||
m_btnWasPressedProp = btnControl.GetProperty("wasPressedThisFrame");
|
||||
|
||||
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");
|
||||
|
||||
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;
|
||||
|
||||
public static Type TMouse => m_tMouse ?? (m_tMouse = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Mouse"));
|
||||
private static Type m_tMouse;
|
||||
|
||||
public static Type TKey => m_tKey ?? (m_tKey = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Key"));
|
||||
private static Type m_tKey;
|
||||
|
||||
private static PropertyInfo m_btnIsPressedProp;
|
||||
private static PropertyInfo m_btnWasPressedProp;
|
||||
|
||||
private static object CurrentKeyboard => m_currentKeyboard ?? (m_currentKeyboard = m_kbCurrentProp.GetValue(null, null));
|
||||
private static object m_currentKeyboard;
|
||||
private static PropertyInfo m_kbCurrentProp;
|
||||
private static PropertyInfo m_kbIndexer;
|
||||
|
||||
private static object CurrentMouse => m_currentMouse ?? (m_currentMouse = m_mouseCurrentProp.GetValue(null, null));
|
||||
private static object m_currentMouse;
|
||||
private static PropertyInfo m_mouseCurrentProp;
|
||||
|
||||
private static object LeftMouseButton => m_lmb ?? (m_lmb = m_leftButtonProp.GetValue(CurrentMouse, null));
|
||||
private static object m_lmb;
|
||||
private static PropertyInfo m_leftButtonProp;
|
||||
|
||||
private static object RightMouseButton => m_rmb ?? (m_rmb = m_rightButtonProp.GetValue(CurrentMouse, null));
|
||||
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 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
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return (Vector2)ReadV2ControlMethod.Invoke(MousePositionInfo, ArgumentUtility.EmptyArgs);
|
||||
}
|
||||
catch { return Vector2.zero; }
|
||||
}
|
||||
}
|
||||
|
||||
public Vector2 MouseScrollDelta
|
||||
{
|
||||
get
|
||||
{
|
||||
try
|
||||
{
|
||||
return (Vector2)ReadV2ControlMethod.Invoke(MouseScrollInfo, ArgumentUtility.EmptyArgs);
|
||||
}
|
||||
catch { return Vector2.zero; }
|
||||
}
|
||||
}
|
||||
|
||||
internal static Dictionary<KeyCode, object> ActualKeyDict = new Dictionary<KeyCode, object>();
|
||||
internal static Dictionary<string, string> enumNameFixes = new Dictionary<string, string>
|
||||
{
|
||||
{ "Control", "Ctrl" },
|
||||
{ "Return", "Enter" },
|
||||
{ "Alpha", "Digit" },
|
||||
{ "Keypad", "Numpad" },
|
||||
{ "Numlock", "NumLock" },
|
||||
{ "Print", "PrintScreen" },
|
||||
{ "BackQuote", "Backquote" }
|
||||
};
|
||||
|
||||
internal object GetActualKey(KeyCode key)
|
||||
{
|
||||
if (!ActualKeyDict.ContainsKey(key))
|
||||
{
|
||||
var s = key.ToString();
|
||||
try
|
||||
{
|
||||
if (enumNameFixes.First(it => s.Contains(it.Key)) is KeyValuePair<string, string> entry)
|
||||
s = s.Replace(entry.Key, entry.Value);
|
||||
}
|
||||
catch { }
|
||||
|
||||
var parsed = Enum.Parse(TKey, s);
|
||||
var actualKey = m_kbIndexer.GetValue(CurrentKeyboard, new object[] { parsed });
|
||||
|
||||
ActualKeyDict.Add(key, actualKey);
|
||||
}
|
||||
|
||||
return ActualKeyDict[key];
|
||||
}
|
||||
|
||||
public bool GetKeyDown(KeyCode key) => (bool)m_btnWasPressedProp.GetValue(GetActualKey(key), null);
|
||||
|
||||
public bool GetKey(KeyCode key) => (bool)m_btnIsPressedProp.GetValue(GetActualKey(key), null);
|
||||
|
||||
public bool GetMouseButtonDown(int btn)
|
||||
{
|
||||
switch (btn)
|
||||
{
|
||||
case 0: return (bool)m_btnWasPressedProp.GetValue(LeftMouseButton, null);
|
||||
case 1: return (bool)m_btnWasPressedProp.GetValue(RightMouseButton, null);
|
||||
// case 2: return (bool)_btnWasPressedProp.GetValue(MiddleMouseButton, null);
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
public bool GetMouseButton(int btn)
|
||||
{
|
||||
switch (btn)
|
||||
{
|
||||
case 0: return (bool)m_btnIsPressedProp.GetValue(LeftMouseButton, null);
|
||||
case 1: return (bool)m_btnIsPressedProp.GetValue(RightMouseButton, null);
|
||||
// case 2: return (bool)_btnIsPressedProp.GetValue(MiddleMouseButton, null);
|
||||
default: throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
|
||||
// UI Input
|
||||
|
||||
public Type TInputSystemUIInputModule
|
||||
=> m_tUIInputModule
|
||||
?? (m_tUIInputModule = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.UI.InputSystemUIInputModule"));
|
||||
internal Type m_tUIInputModule;
|
||||
|
||||
public BaseInputModule UIModule => m_newInputModule;
|
||||
internal BaseInputModule m_newInputModule;
|
||||
|
||||
public void AddUIInputModule()
|
||||
{
|
||||
if (TInputSystemUIInputModule == null)
|
||||
{
|
||||
ExplorerCore.LogWarning("Unable to find UI Input Module Type, Input will not work!");
|
||||
return;
|
||||
}
|
||||
|
||||
var assetType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionAsset");
|
||||
m_newInputModule = RuntimeProvider.Instance.AddComponent<BaseInputModule>(UIManager.CanvasRoot, TInputSystemUIInputModule);
|
||||
var asset = RuntimeProvider.Instance.CreateScriptable(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" })
|
||||
.TryCast(ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionMap"));
|
||||
|
||||
CreateAction(map, "point", new[] { "<Mouse>/position" }, "point");
|
||||
CreateAction(map, "click", new[] { "<Mouse>/leftButton" }, "leftClick");
|
||||
CreateAction(map, "rightClick", new[] { "<Mouse>/rightButton" }, "rightClick");
|
||||
CreateAction(map, "scrollWheel", new[] { "<Mouse>/scroll" }, "scrollWheel");
|
||||
|
||||
UI_Enable = map.GetType().GetMethod("Enable");
|
||||
UI_Enable.Invoke(map, ArgumentUtility.EmptyArgs);
|
||||
UI_ActionMap = map;
|
||||
}
|
||||
|
||||
private Type inputExtensions;
|
||||
private object UI_ActionMap;
|
||||
private MethodInfo UI_Enable;
|
||||
|
||||
private void CreateAction(object map, string actionName, string[] bindings, string propertyName)
|
||||
{
|
||||
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 })
|
||||
.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.TryCast(inputActionType), binding, null, null, null });
|
||||
|
||||
var refType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionReference");
|
||||
var inputRef = refType.GetMethod("Create")
|
||||
.Invoke(null, new object[] { action })
|
||||
.TryCast(refType);
|
||||
|
||||
TInputSystemUIInputModule
|
||||
.GetProperty(propertyName)
|
||||
.SetValue(m_newInputModule.TryCast(TInputSystemUIInputModule), inputRef, null);
|
||||
}
|
||||
|
||||
public void ActivateModule()
|
||||
{
|
||||
m_newInputModule.ActivateModule();
|
||||
UI_Enable.Invoke(UI_ActionMap, ArgumentUtility.EmptyArgs);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.Core.Input
|
||||
{
|
||||
public class LegacyInput : IHandleInput
|
||||
{
|
||||
public LegacyInput()
|
||||
{
|
||||
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) });
|
||||
m_getMouseButtonDownMethod = TInput.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) });
|
||||
}
|
||||
|
||||
public static Type TInput => m_tInput ?? (m_tInput = ReflectionUtility.GetTypeByName("UnityEngine.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;
|
||||
private static MethodInfo m_getMouseButtonDownMethod;
|
||||
|
||||
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 });
|
||||
|
||||
public bool GetMouseButton(int btn) => (bool)m_getMouseButtonMethod.Invoke(null, new object[] { btn });
|
||||
|
||||
public bool GetMouseButtonDown(int btn) => (bool)m_getMouseButtonDownMethod.Invoke(null, new object[] { btn });
|
||||
|
||||
// UI Input module
|
||||
|
||||
public BaseInputModule UIModule => m_inputModule;
|
||||
internal StandaloneInputModule m_inputModule;
|
||||
|
||||
public void AddUIInputModule()
|
||||
{
|
||||
m_inputModule = UIManager.CanvasRoot.gameObject.AddComponent<StandaloneInputModule>();
|
||||
}
|
||||
|
||||
public void ActivateModule()
|
||||
{
|
||||
m_inputModule.ActivateModule();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,23 +0,0 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UnityExplorer.Core.Input
|
||||
{
|
||||
// Just a stub for games where no Input module was able to load at all.
|
||||
|
||||
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;
|
||||
|
||||
public bool GetMouseButton(int btn) => false;
|
||||
public bool GetMouseButtonDown(int btn) => false;
|
||||
|
||||
public BaseInputModule UIModule => null;
|
||||
public void ActivateModule() { }
|
||||
public void AddUIInputModule() { }
|
||||
}
|
||||
}
|
@ -1,116 +0,0 @@
|
||||
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<T>(this object obj)
|
||||
{
|
||||
try
|
||||
{
|
||||
return (T)ReflectionUtility.Instance.Internal_TryCast(obj, typeof(T));
|
||||
}
|
||||
catch
|
||||
{
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public static HashSet<Type> GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric)
|
||||
=> ReflectionUtility.GetImplementationsOf(baseType, allowAbstract, allowGeneric);
|
||||
|
||||
// ------- Misc extensions --------
|
||||
|
||||
/// <summary>
|
||||
/// Safely try to get all Types inside an Assembly.
|
||||
/// </summary>
|
||||
public static IEnumerable<Type> 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<Type>();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Check if the two objects are reference-equal, including checking for UnityEngine.Object-equality and Il2CppSystem.Object-equality.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Helper to display a simple "{ExceptionType}: {Message}" of the exception, and optionally use the inner-most exception.
|
||||
/// </summary>
|
||||
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 != null)
|
||||
{
|
||||
if (e.InnerException == null)
|
||||
break;
|
||||
#if CPP
|
||||
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException)
|
||||
break;
|
||||
#endif
|
||||
e = e.InnerException;
|
||||
}
|
||||
|
||||
return e;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,967 +0,0 @@
|
||||
#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;
|
||||
using UnhollowerBaseLib.Attributes;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public class Il2CppReflection : ReflectionUtility
|
||||
{
|
||||
protected override void Initialize()
|
||||
{
|
||||
base.Initialize();
|
||||
|
||||
float start = Time.realtimeSinceStartup;
|
||||
TryLoadGameModules();
|
||||
ExplorerCore.Log($"Loaded Unhollowed modules in {Time.realtimeSinceStartup - start} seconds");
|
||||
|
||||
start = Time.realtimeSinceStartup;
|
||||
BuildDeobfuscationCache();
|
||||
OnTypeLoaded += TryCacheDeobfuscatedType;
|
||||
ExplorerCore.Log($"Setup IL2CPP reflection in {Time.realtimeSinceStartup - start} seconds, " +
|
||||
$"deobfuscated types count: {DeobfuscatedTypes.Count}");
|
||||
}
|
||||
|
||||
#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))
|
||||
{
|
||||
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<string, Type> DeobfuscatedTypes = new Dictionary<string, Type>();
|
||||
private static readonly Dictionary<string, string> reverseDeobCache = new Dictionary<string, string>();
|
||||
|
||||
private static void BuildDeobfuscationCache()
|
||||
{
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
{
|
||||
foreach (var type in asm.TryGetTypes())
|
||||
TryCacheDeobfuscatedType(type);
|
||||
}
|
||||
}
|
||||
|
||||
private static void TryCacheDeobfuscatedType(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!type.CustomAttributes.Any())
|
||||
return;
|
||||
|
||||
foreach (var att in type.CustomAttributes)
|
||||
{
|
||||
// Thanks to Slaynash for this
|
||||
|
||||
if (att.AttributeType == typeof(ObfuscatedNameAttribute))
|
||||
{
|
||||
string obfuscatedName = att.ConstructorArguments[0].Value.ToString();
|
||||
|
||||
DeobfuscatedTypes.Add(obfuscatedName, type);
|
||||
reverseDeobCache.Add(type.FullName, obfuscatedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
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<string, IntPtr> cppClassPointers = new Dictionary<string, IntPtr>();
|
||||
|
||||
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).TryCast(castTo);
|
||||
}
|
||||
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);
|
||||
|
||||
if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr))
|
||||
return obj;
|
||||
|
||||
// Casting from il2cpp object to il2cpp object...
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
//private static bool IsAssignableFrom(Type thisType, Type fromType)
|
||||
//{
|
||||
// if (!Il2CppTypeNotNull(fromType, out IntPtr fromTypePtr)
|
||||
// || !Il2CppTypeNotNull(thisType, out IntPtr thisTypePtr))
|
||||
// {
|
||||
// // one or both of the types are not Il2Cpp types, use normal check
|
||||
// return thisType.IsAssignableFrom(fromType);
|
||||
// }
|
||||
//
|
||||
// return il2cpp_class_is_assignable_from(thisTypePtr, fromTypePtr);
|
||||
//}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Boxing and unboxing ValueTypes
|
||||
|
||||
// cached il2cpp unbox methods
|
||||
internal static readonly Dictionary<string, MethodInfo> unboxMethods = new Dictionary<string, MethodInfo>();
|
||||
|
||||
// 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)
|
||||
{
|
||||
// Check for nullable enums
|
||||
var type = cppObj.GetType();
|
||||
if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Il2CppSystem.Nullable<>))
|
||||
{
|
||||
var nullable = cppObj.TryCast(type);
|
||||
var nullableHasValueProperty = type.GetProperty("HasValue");
|
||||
if ((bool)nullableHasValueProperty.GetValue(nullable, null))
|
||||
{
|
||||
// nullable has a value.
|
||||
var nullableValueProperty = type.GetProperty("Value");
|
||||
return Enum.Parse(toType, nullableValueProperty.GetValue(nullable, null).ToString());
|
||||
}
|
||||
// nullable and no current value.
|
||||
return cppObj;
|
||||
}
|
||||
|
||||
return Enum.Parse(toType, cppObj.ToString());
|
||||
}
|
||||
|
||||
// Not enum, unbox with Il2CppObjectBase.Unbox
|
||||
|
||||
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<string, Type> il2cppPrimitivesToMono = new Dictionary<string, Type>
|
||||
{
|
||||
{ "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<object> instances)
|
||||
{
|
||||
PropertyInfo pi;
|
||||
foreach (var name in possibleNames)
|
||||
{
|
||||
pi = type.GetProperty(name, flags);
|
||||
if (pi != null)
|
||||
{
|
||||
var instance = pi.GetValue(null, null);
|
||||
if (instance != null)
|
||||
{
|
||||
instances.Add(instance);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
base.Internal_FindSingleton(possibleNames, type, flags, instances);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#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 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()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
ExplorerCore.LogWarning($"Expected Unhollowed folder path does not exist: '{UnhollowedFolderPath}'");
|
||||
}
|
||||
|
||||
internal bool DoLoadModule(string fullPath, bool suppressWarning = false)
|
||||
{
|
||||
if (!File.Exists(fullPath))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
Assembly.LoadFile(fullPath);
|
||||
//Assembly.Load(File.ReadAllBytes(fullPath));
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
Console.WriteLine($"Failed loading module '{Path.GetFileName(fullPath)}'! {e.ReflectionExToString()}");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Il2cpp reflection blacklist
|
||||
|
||||
public override string[] DefaultReflectionBlacklist => defaultIl2CppBlacklist.ToArray();
|
||||
|
||||
// These methods currently cause a crash in most il2cpp games,
|
||||
// even from doing "GetParameters()" on the MemberInfo.
|
||||
// Blacklisting until the issue is fixed in Unhollower.
|
||||
public static HashSet<string> defaultIl2CppBlacklist = new HashSet<string>
|
||||
{
|
||||
// These were deprecated a long time ago, still show up in some IL2CPP games for some reason
|
||||
"UnityEngine.MonoBehaviour.allowPrefabModeInPlayMode",
|
||||
"UnityEngine.MonoBehaviour.runInEditMode",
|
||||
"UnityEngine.Component.animation",
|
||||
"UnityEngine.Component.audio",
|
||||
"UnityEngine.Component.camera",
|
||||
"UnityEngine.Component.collider",
|
||||
"UnityEngine.Component.collider2D",
|
||||
"UnityEngine.Component.constantForce",
|
||||
"UnityEngine.Component.hingeJoint",
|
||||
"UnityEngine.Component.light",
|
||||
"UnityEngine.Component.networkView",
|
||||
"UnityEngine.Component.particleSystem",
|
||||
"UnityEngine.Component.renderer",
|
||||
"UnityEngine.Component.rigidbody",
|
||||
"UnityEngine.Component.rigidbody2D",
|
||||
"UnityEngine.Light.flare",
|
||||
// These can cause a crash in IL2CPP
|
||||
"Il2CppSystem.Type.DeclaringMethod",
|
||||
"Il2CppSystem.RuntimeType.DeclaringMethod",
|
||||
"Unity.Jobs.LowLevel.Unsafe.JobsUtility.CreateJobReflectionData",
|
||||
"Unity.Profiling.ProfilerRecorder.CopyTo",
|
||||
"Unity.Profiling.ProfilerRecorder.StartNew",
|
||||
"UnityEngine.Analytics.Analytics.RegisterEvent",
|
||||
"UnityEngine.Analytics.Analytics.SendEvent",
|
||||
"UnityEngine.Analytics.ContinuousEvent+ConfigureEventDelegate.Invoke",
|
||||
"UnityEngine.Analytics.ContinuousEvent.ConfigureEvent",
|
||||
"UnityEngine.Animations.AnimationLayerMixerPlayable.Create",
|
||||
"UnityEngine.Animations.AnimationLayerMixerPlayable.CreateHandle",
|
||||
"UnityEngine.Animations.AnimationMixerPlayable.Create",
|
||||
"UnityEngine.Animations.AnimationMixerPlayable.CreateHandle",
|
||||
"UnityEngine.AssetBundle.RecompressAssetBundleAsync",
|
||||
"UnityEngine.Audio.AudioMixerPlayable.Create",
|
||||
"UnityEngine.BoxcastCommand.ScheduleBatch",
|
||||
"UnityEngine.Camera.CalculateProjectionMatrixFromPhysicalProperties",
|
||||
"UnityEngine.CapsulecastCommand.ScheduleBatch",
|
||||
"UnityEngine.Collider2D.Cast",
|
||||
"UnityEngine.Collider2D.Raycast",
|
||||
"UnityEngine.ComputeBuffer+BeginBufferWriteDelegate.Invoke",
|
||||
"UnityEngine.ComputeBuffer+EndBufferWriteDelegate.Invoke",
|
||||
"UnityEngine.ComputeBuffer.BeginBufferWrite",
|
||||
"UnityEngine.ComputeBuffer.EndBufferWrite",
|
||||
"UnityEngine.Cubemap+SetPixelDataImplArrayDelegate.Invoke",
|
||||
"UnityEngine.Cubemap+SetPixelDataImplDelegate.Invoke",
|
||||
"UnityEngine.Cubemap.SetPixelDataImpl",
|
||||
"UnityEngine.Cubemap.SetPixelDataImplArray",
|
||||
"UnityEngine.CubemapArray+SetPixelDataImplArrayDelegate.Invoke",
|
||||
"UnityEngine.CubemapArray+SetPixelDataImplDelegate.Invoke",
|
||||
"UnityEngine.CubemapArray.SetPixelDataImpl",
|
||||
"UnityEngine.CubemapArray.SetPixelDataImplArray",
|
||||
"UnityEngine.Experimental.Playables.MaterialEffectPlayable.Create",
|
||||
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure+AddInstanceDelegate.Invoke",
|
||||
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure+AddInstance_Procedural_InjectedDelegate.Invoke",
|
||||
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance",
|
||||
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance_Procedural",
|
||||
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance_Procedural_Injected",
|
||||
"UnityEngine.Experimental.Rendering.RayTracingShader+DispatchDelegate.Invoke",
|
||||
"UnityEngine.Experimental.Rendering.RayTracingShader.Dispatch",
|
||||
"UnityEngine.Experimental.Rendering.RenderPassAttachment.Clear",
|
||||
"UnityEngine.GUI.DoButtonGrid",
|
||||
"UnityEngine.GUI.Slider",
|
||||
"UnityEngine.GUI.Toolbar",
|
||||
"UnityEngine.Graphics.DrawMeshInstancedIndirect",
|
||||
"UnityEngine.Graphics.DrawMeshInstancedProcedural",
|
||||
"UnityEngine.Graphics.DrawProcedural",
|
||||
"UnityEngine.Graphics.DrawProceduralIndirect",
|
||||
"UnityEngine.Graphics.DrawProceduralIndirectNow",
|
||||
"UnityEngine.Graphics.DrawProceduralNow",
|
||||
"UnityEngine.LineRenderer+BakeMeshDelegate.Invoke",
|
||||
"UnityEngine.LineRenderer.BakeMesh",
|
||||
"UnityEngine.Mesh.GetIndices",
|
||||
"UnityEngine.Mesh.GetTriangles",
|
||||
"UnityEngine.Mesh.SetIndices",
|
||||
"UnityEngine.Mesh.SetTriangles",
|
||||
"UnityEngine.Physics2D.BoxCast",
|
||||
"UnityEngine.Physics2D.CapsuleCast",
|
||||
"UnityEngine.Physics2D.CircleCast",
|
||||
"UnityEngine.PhysicsScene.BoxCast",
|
||||
"UnityEngine.PhysicsScene.CapsuleCast",
|
||||
"UnityEngine.PhysicsScene.OverlapBox",
|
||||
"UnityEngine.PhysicsScene.OverlapCapsule",
|
||||
"UnityEngine.PhysicsScene.SphereCast",
|
||||
"UnityEngine.PhysicsScene2D.BoxCast",
|
||||
"UnityEngine.PhysicsScene2D.CapsuleCast",
|
||||
"UnityEngine.PhysicsScene2D.CircleCast",
|
||||
"UnityEngine.PhysicsScene2D.GetRayIntersection",
|
||||
"UnityEngine.PhysicsScene2D.Linecast",
|
||||
"UnityEngine.PhysicsScene2D.OverlapArea",
|
||||
"UnityEngine.PhysicsScene2D.OverlapBox",
|
||||
"UnityEngine.PhysicsScene2D.OverlapCapsule",
|
||||
"UnityEngine.PhysicsScene2D.OverlapCircle",
|
||||
"UnityEngine.PhysicsScene2D.OverlapCollider",
|
||||
"UnityEngine.PhysicsScene2D.OverlapPoint",
|
||||
"UnityEngine.PhysicsScene2D.Raycast",
|
||||
"UnityEngine.Playables.Playable.Create",
|
||||
"UnityEngine.Profiling.CustomSampler.Create",
|
||||
"UnityEngine.RaycastCommand.ScheduleBatch",
|
||||
"UnityEngine.RemoteConfigSettings+QueueConfigDelegate.Invoke",
|
||||
"UnityEngine.RemoteConfigSettings.QueueConfig",
|
||||
"UnityEngine.RenderTexture.GetTemporaryImpl",
|
||||
"UnityEngine.Rendering.AsyncGPUReadback.Request",
|
||||
"UnityEngine.Rendering.AttachmentDescriptor.ConfigureClear",
|
||||
"UnityEngine.Rendering.BatchRendererGroup+AddBatch_InjectedDelegate.Invoke",
|
||||
"UnityEngine.Rendering.BatchRendererGroup.AddBatch",
|
||||
"UnityEngine.Rendering.BatchRendererGroup.AddBatch_Injected",
|
||||
"UnityEngine.Rendering.CommandBuffer+Internal_DispatchRaysDelegate.Invoke",
|
||||
"UnityEngine.Rendering.CommandBuffer.DispatchRays",
|
||||
"UnityEngine.Rendering.CommandBuffer.DrawMeshInstancedProcedural",
|
||||
"UnityEngine.Rendering.CommandBuffer.Internal_DispatchRays",
|
||||
"UnityEngine.Rendering.CommandBuffer.ResolveAntiAliasedSurface",
|
||||
"UnityEngine.Rendering.ScriptableRenderContext.BeginRenderPass",
|
||||
"UnityEngine.Rendering.ScriptableRenderContext.BeginScopedRenderPass",
|
||||
"UnityEngine.Rendering.ScriptableRenderContext.BeginScopedSubPass",
|
||||
"UnityEngine.Rendering.ScriptableRenderContext.BeginSubPass",
|
||||
"UnityEngine.Rendering.ScriptableRenderContext.SetupCameraProperties",
|
||||
"UnityEngine.Rigidbody2D.Cast",
|
||||
"UnityEngine.Scripting.GarbageCollector+CollectIncrementalDelegate.Invoke",
|
||||
"UnityEngine.Scripting.GarbageCollector.CollectIncremental",
|
||||
"UnityEngine.SpherecastCommand.ScheduleBatch",
|
||||
"UnityEngine.Texture.GetPixelDataSize",
|
||||
"UnityEngine.Texture.GetPixelDataOffset",
|
||||
"UnityEngine.Texture.GetPixelDataOffset",
|
||||
"UnityEngine.Texture2D+SetPixelDataImplArrayDelegate.Invoke",
|
||||
"UnityEngine.Texture2D+SetPixelDataImplDelegate.Invoke",
|
||||
"UnityEngine.Texture2D.SetPixelDataImpl",
|
||||
"UnityEngine.Texture2D.SetPixelDataImplArray",
|
||||
"UnityEngine.Texture2DArray+SetPixelDataImplArrayDelegate.Invoke",
|
||||
"UnityEngine.Texture2DArray+SetPixelDataImplDelegate.Invoke",
|
||||
"UnityEngine.Texture2DArray.SetPixelDataImpl",
|
||||
"UnityEngine.Texture2DArray.SetPixelDataImplArray",
|
||||
"UnityEngine.Texture3D+SetPixelDataImplArrayDelegate.Invoke",
|
||||
"UnityEngine.Texture3D+SetPixelDataImplDelegate.Invoke",
|
||||
"UnityEngine.Texture3D.SetPixelDataImpl",
|
||||
"UnityEngine.Texture3D.SetPixelDataImplArray",
|
||||
"UnityEngine.TrailRenderer+BakeMeshDelegate.Invoke",
|
||||
"UnityEngine.TrailRenderer.BakeMesh",
|
||||
"UnityEngine.WWW.LoadFromCacheOrDownload",
|
||||
"UnityEngine.XR.InputDevice.SendHapticImpulse",
|
||||
};
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region IL2CPP IEnumerable and IDictionary
|
||||
|
||||
protected override bool Internal_TryGetEntryType(Type enumerableType, out Type type)
|
||||
{
|
||||
// Check for system types (not unhollowed)
|
||||
if (base.Internal_TryGetEntryType(enumerableType, out type))
|
||||
return true;
|
||||
|
||||
// Type is either an IL2CPP enumerable, or its not generic.
|
||||
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
// Temporary naive solution until IL2CPP interface support improves.
|
||||
// This will work fine for most cases, but there are edge cases which would not work.
|
||||
type = type.GetGenericArguments()[0];
|
||||
return true;
|
||||
}
|
||||
|
||||
// Unable to determine entry type
|
||||
type = typeof(object);
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool Internal_TryGetEntryTypes(Type type, out Type keys, out Type values)
|
||||
{
|
||||
if (base.Internal_TryGetEntryTypes(type, out keys, out values))
|
||||
return true;
|
||||
|
||||
// Type is either an IL2CPP dictionary, or its not generic.
|
||||
if (type.IsGenericType)
|
||||
{
|
||||
// Naive solution until IL2CPP interfaces improve.
|
||||
var args = type.GetGenericArguments();
|
||||
if (args.Length == 2)
|
||||
{
|
||||
keys = args[0];
|
||||
values = args[1];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
keys = typeof(object);
|
||||
values = typeof(object);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Temp fix until Unhollower interface support improves
|
||||
|
||||
internal static readonly Dictionary<string, MethodInfo> getEnumeratorMethods = new Dictionary<string, MethodInfo>();
|
||||
internal static readonly Dictionary<string, EnumeratorInfo> enumeratorInfos = new Dictionary<string, EnumeratorInfo>();
|
||||
internal static readonly HashSet<string> notSupportedTypes = new HashSet<string>();
|
||||
|
||||
// IEnumerables
|
||||
|
||||
internal static IntPtr cppIEnumerablePointer;
|
||||
|
||||
protected override bool Internal_IsEnumerable(Type type)
|
||||
{
|
||||
if (base.Internal_IsEnumerable(type))
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
if (cppIEnumerablePointer == IntPtr.Zero)
|
||||
Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IEnumerable), out cppIEnumerablePointer);
|
||||
|
||||
if (cppIEnumerablePointer != IntPtr.Zero
|
||||
&& Il2CppTypeNotNull(type, out IntPtr assignFromPtr)
|
||||
&& il2cpp_class_is_assignable_from(cppIEnumerablePointer, assignFromPtr))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
catch { }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
internal class EnumeratorInfo
|
||||
{
|
||||
internal MethodInfo moveNext;
|
||||
internal PropertyInfo current;
|
||||
}
|
||||
|
||||
protected override bool Internal_TryGetEnumerator(object list, out IEnumerator enumerator)
|
||||
{
|
||||
if (list is IEnumerable)
|
||||
return base.Internal_TryGetEnumerator(list, out enumerator);
|
||||
|
||||
try
|
||||
{
|
||||
PrepareCppEnumerator(list, out object cppEnumerator, out EnumeratorInfo info);
|
||||
enumerator = EnumerateCppList(info, cppEnumerator);
|
||||
return true;
|
||||
}
|
||||
catch //(Exception ex)
|
||||
{
|
||||
//ExplorerCore.LogWarning($"Exception enumerating IEnumerable: {ex.ReflectionExToString()}");
|
||||
enumerator = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrepareCppEnumerator(object list, out object cppEnumerator, out EnumeratorInfo info)
|
||||
{
|
||||
info = null;
|
||||
cppEnumerator = null;
|
||||
if (list == null)
|
||||
throw new ArgumentNullException("list");
|
||||
|
||||
// Some ugly reflection to use the il2cpp interface for the instance type
|
||||
|
||||
var type = list.GetType();
|
||||
var key = type.AssemblyQualifiedName;
|
||||
|
||||
if (!getEnumeratorMethods.ContainsKey(key))
|
||||
{
|
||||
getEnumeratorMethods.Add(key, type.GetMethod("GetEnumerator"));
|
||||
|
||||
// ensure the enumerator type is supported
|
||||
try
|
||||
{
|
||||
var test = getEnumeratorMethods[key].Invoke(list, null);
|
||||
test.GetType().GetMethod("MoveNext").Invoke(test, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
notSupportedTypes.Add(key);
|
||||
}
|
||||
}
|
||||
|
||||
if (notSupportedTypes.Contains(key))
|
||||
throw new NotSupportedException($"The IEnumerable type '{type.FullName}' does not support MoveNext.");
|
||||
|
||||
cppEnumerator = getEnumeratorMethods[key].Invoke(list, null);
|
||||
var enumeratorType = cppEnumerator.GetType();
|
||||
|
||||
var enumInfoKey = enumeratorType.AssemblyQualifiedName;
|
||||
|
||||
if (!enumeratorInfos.ContainsKey(enumInfoKey))
|
||||
{
|
||||
enumeratorInfos.Add(enumInfoKey, new EnumeratorInfo
|
||||
{
|
||||
current = enumeratorType.GetProperty("Current"),
|
||||
moveNext = enumeratorType.GetMethod("MoveNext"),
|
||||
});
|
||||
}
|
||||
|
||||
info = enumeratorInfos[enumInfoKey];
|
||||
}
|
||||
|
||||
internal static IEnumerator EnumerateCppList(EnumeratorInfo info, object enumerator)
|
||||
{
|
||||
// Yield and return the actual entries
|
||||
while ((bool)info.moveNext.Invoke(enumerator, null))
|
||||
yield return info.current.GetValue(enumerator);
|
||||
}
|
||||
|
||||
// IDictionary
|
||||
|
||||
internal static IntPtr cppIDictionaryPointer;
|
||||
|
||||
protected override bool Internal_IsDictionary(Type type)
|
||||
{
|
||||
if (base.Internal_IsDictionary(type))
|
||||
return true;
|
||||
|
||||
try
|
||||
{
|
||||
if (cppIDictionaryPointer == IntPtr.Zero)
|
||||
if (!Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IDictionary), out cppIDictionaryPointer))
|
||||
return false;
|
||||
|
||||
if (Il2CppTypeNotNull(type, out IntPtr classPtr)
|
||||
&& il2cpp_class_is_assignable_from(cppIDictionaryPointer, classPtr))
|
||||
return true;
|
||||
}
|
||||
catch { }
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected override bool Internal_TryGetDictEnumerator(object dictionary, out IEnumerator<DictionaryEntry> dictEnumerator)
|
||||
{
|
||||
if (dictionary is IDictionary)
|
||||
return base.Internal_TryGetDictEnumerator(dictionary, out dictEnumerator);
|
||||
|
||||
try
|
||||
{
|
||||
var type = dictionary.GetType();
|
||||
|
||||
if (typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(type))
|
||||
{
|
||||
dictEnumerator = EnumerateCppHashTable(dictionary.TryCast<Il2CppSystem.Collections.Hashtable>());
|
||||
return true;
|
||||
}
|
||||
|
||||
var keys = type.GetProperty("Keys").GetValue(dictionary, null);
|
||||
|
||||
var keyCollType = keys.GetType();
|
||||
var cacheKey = keys.GetType().AssemblyQualifiedName;
|
||||
if (!getEnumeratorMethods.ContainsKey(cacheKey))
|
||||
{
|
||||
getEnumeratorMethods.Add(cacheKey, keyCollType.GetMethod("GetEnumerator"));
|
||||
|
||||
// test support
|
||||
try
|
||||
{
|
||||
var test = getEnumeratorMethods[cacheKey].Invoke(keys, null);
|
||||
test.GetType().GetMethod("MoveNext").Invoke(test, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
notSupportedTypes.Add(cacheKey);
|
||||
}
|
||||
}
|
||||
|
||||
if (notSupportedTypes.Contains(cacheKey))
|
||||
throw new Exception($"The IDictionary type '{type.FullName}' does not support MoveNext.");
|
||||
|
||||
var keyEnumerator = getEnumeratorMethods[cacheKey].Invoke(keys, null);
|
||||
var keyInfo = new EnumeratorInfo
|
||||
{
|
||||
current = keyEnumerator.GetType().GetProperty("Current"),
|
||||
moveNext = keyEnumerator.GetType().GetMethod("MoveNext"),
|
||||
};
|
||||
|
||||
var values = type.GetProperty("Values").GetValue(dictionary, null);
|
||||
var valueEnumerator = values.GetType().GetMethod("GetEnumerator").Invoke(values, null);
|
||||
var valueInfo = new EnumeratorInfo
|
||||
{
|
||||
current = valueEnumerator.GetType().GetProperty("Current"),
|
||||
moveNext = valueEnumerator.GetType().GetMethod("MoveNext"),
|
||||
};
|
||||
|
||||
dictEnumerator = EnumerateCppDict(keyInfo, keyEnumerator, valueInfo, valueEnumerator);
|
||||
return true;
|
||||
}
|
||||
catch //(Exception ex)
|
||||
{
|
||||
//ExplorerCore.LogWarning($"Exception enumerating IDictionary: {ex.ReflectionExToString()}");
|
||||
dictEnumerator = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerator<DictionaryEntry> EnumerateCppDict(EnumeratorInfo keyInfo, object keyEnumerator,
|
||||
EnumeratorInfo valueInfo, object valueEnumerator)
|
||||
{
|
||||
while ((bool)keyInfo.moveNext.Invoke(keyEnumerator, null))
|
||||
{
|
||||
valueInfo.moveNext.Invoke(valueEnumerator, null);
|
||||
|
||||
var key = keyInfo.current.GetValue(keyEnumerator, null);
|
||||
var value = valueInfo.current.GetValue(valueEnumerator, null);
|
||||
|
||||
yield return new DictionaryEntry(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
internal static IEnumerator<DictionaryEntry> EnumerateCppHashTable(Il2CppSystem.Collections.Hashtable hashtable)
|
||||
{
|
||||
for (int i = 0; i < hashtable.buckets.Count; i++)
|
||||
{
|
||||
var bucket = hashtable.buckets[i];
|
||||
if (bucket == null || bucket.key == null)
|
||||
continue;
|
||||
|
||||
yield return new DictionaryEntry(bucket.key, bucket.val);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -1,596 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using BF = System.Reflection.BindingFlags;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public class ReflectionUtility
|
||||
{
|
||||
public const BF FLAGS = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
|
||||
|
||||
internal static ReflectionUtility Instance;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
Instance =
|
||||
#if CPP
|
||||
new Il2CppReflection();
|
||||
#else
|
||||
new ReflectionUtility();
|
||||
#endif
|
||||
Instance.Initialize();
|
||||
}
|
||||
|
||||
protected virtual void Initialize()
|
||||
{
|
||||
SetupTypeCache();
|
||||
|
||||
LoadBlacklistString(ConfigManager.Reflection_Signature_Blacklist.Value);
|
||||
ConfigManager.Reflection_Signature_Blacklist.OnValueChanged += LoadBlacklistString;
|
||||
}
|
||||
|
||||
#region Type cache
|
||||
|
||||
public static Action<Type> OnTypeLoaded;
|
||||
|
||||
/// <summary>Key: Type.FullName</summary>
|
||||
protected static readonly SortedDictionary<string, Type> AllTypes = new SortedDictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public static readonly List<string> AllNamespaces = new List<string>();
|
||||
private static readonly HashSet<string> uniqueNamespaces = new HashSet<string>();
|
||||
|
||||
private static string[] allTypesArray;
|
||||
public static string[] GetTypeNameArray()
|
||||
{
|
||||
if (allTypesArray == null || allTypesArray.Length != AllTypes.Count)
|
||||
{
|
||||
allTypesArray = new string[AllTypes.Count];
|
||||
int i = 0;
|
||||
foreach (var name in AllTypes.Keys)
|
||||
{
|
||||
allTypesArray[i] = name;
|
||||
i++;
|
||||
}
|
||||
}
|
||||
return allTypesArray;
|
||||
}
|
||||
|
||||
private static void SetupTypeCache()
|
||||
{
|
||||
float start = Time.realtimeSinceStartup;
|
||||
|
||||
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
|
||||
CacheTypes(asm);
|
||||
|
||||
AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
|
||||
|
||||
ExplorerCore.Log($"Cached AppDomain assemblies in {Time.realtimeSinceStartup - start} seconds");
|
||||
}
|
||||
|
||||
private static void AssemblyLoaded(object sender, AssemblyLoadEventArgs args)
|
||||
{
|
||||
if (args.LoadedAssembly == null || args.LoadedAssembly.GetName().Name == "completions")
|
||||
return;
|
||||
|
||||
CacheTypes(args.LoadedAssembly);
|
||||
}
|
||||
|
||||
private static void CacheTypes(Assembly asm)
|
||||
{
|
||||
foreach (var type in asm.TryGetTypes())
|
||||
{
|
||||
// Cache namespace if there is one
|
||||
if (!string.IsNullOrEmpty(type.Namespace) && !uniqueNamespaces.Contains(type.Namespace))
|
||||
{
|
||||
uniqueNamespaces.Add(type.Namespace);
|
||||
int i = 0;
|
||||
while (i < AllNamespaces.Count)
|
||||
{
|
||||
if (type.Namespace.CompareTo(AllNamespaces[i]) < 0)
|
||||
break;
|
||||
i++;
|
||||
}
|
||||
AllNamespaces.Insert(i, type.Namespace);
|
||||
}
|
||||
|
||||
// Cache the type. Overwrite type if one exists with the full name
|
||||
if (AllTypes.ContainsKey(type.FullName))
|
||||
AllTypes[type.FullName] = type;
|
||||
else
|
||||
AllTypes.Add(type.FullName, type);
|
||||
|
||||
// Invoke listener
|
||||
OnTypeLoaded?.Invoke(type);
|
||||
|
||||
// Check type inheritance cache, add this to any lists it should be in
|
||||
foreach (var key in typeInheritance.Keys)
|
||||
{
|
||||
try
|
||||
{
|
||||
var baseType = AllTypes[key];
|
||||
if (baseType.IsAssignableFrom(type) && !typeInheritance[key].Contains(type))
|
||||
typeInheritance[key].Add(type);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Find a <see cref="Type"/> in the current AppDomain whose <see cref="Type.FullName"/> matches the provided <paramref name="fullName"/>.
|
||||
/// </summary>
|
||||
/// <param name="fullName">The <see cref="Type.FullName"/> you want to search for - case sensitive and full matches only.</param>
|
||||
/// <returns>The Type if found, otherwise null.</returns>
|
||||
public static Type GetTypeByName(string fullName)
|
||||
=> Instance.Internal_GetTypeByName(fullName);
|
||||
|
||||
internal virtual Type Internal_GetTypeByName(string fullName)
|
||||
{
|
||||
AllTypes.TryGetValue(fullName, out Type type);
|
||||
return type;
|
||||
}
|
||||
|
||||
// Getting the actual type of an object
|
||||
internal virtual Type Internal_GetActualType(object obj)
|
||||
=> obj?.GetType();
|
||||
|
||||
// Force-casting an object to a type
|
||||
internal virtual object Internal_TryCast(object obj, Type castTo)
|
||||
=> obj;
|
||||
|
||||
// Processing deobfuscated type names in strings
|
||||
public static string ProcessTypeInString(Type type, string theString)
|
||||
=> Instance.Internal_ProcessTypeInString(theString, type);
|
||||
|
||||
internal virtual string Internal_ProcessTypeInString(string theString, Type type)
|
||||
=> theString;
|
||||
|
||||
// Singleton finder
|
||||
|
||||
public static void FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances)
|
||||
=> Instance.Internal_FindSingleton(possibleNames, type, flags, instances);
|
||||
|
||||
internal virtual void Internal_FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances)
|
||||
{
|
||||
// Look for a typical Instance backing field.
|
||||
FieldInfo fi;
|
||||
foreach (var name in possibleNames)
|
||||
{
|
||||
fi = type.GetField(name, flags);
|
||||
if (fi != null)
|
||||
{
|
||||
var instance = fi.GetValue(null);
|
||||
if (instance != null)
|
||||
{
|
||||
instances.Add(instance);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Universal helpers
|
||||
|
||||
#region Type inheritance cache
|
||||
|
||||
// cache for GetBaseTypes
|
||||
internal static readonly Dictionary<string, Type[]> baseTypes = new Dictionary<string, Type[]>();
|
||||
|
||||
/// <summary>
|
||||
/// Get all base types of the provided Type, including itself.
|
||||
/// </summary>
|
||||
public static Type[] GetAllBaseTypes(object obj) => GetAllBaseTypes(obj?.GetActualType());
|
||||
|
||||
/// <summary>
|
||||
/// Get all base types of the provided Type, including itself.
|
||||
/// </summary>
|
||||
public static Type[] GetAllBaseTypes(Type type)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException("type");
|
||||
|
||||
var name = type.AssemblyQualifiedName;
|
||||
|
||||
if (baseTypes.TryGetValue(name, out Type[] ret))
|
||||
return ret;
|
||||
|
||||
List<Type> list = new List<Type>();
|
||||
|
||||
while (type != null)
|
||||
{
|
||||
list.Add(type);
|
||||
type = type.BaseType;
|
||||
}
|
||||
|
||||
ret = list.ToArray();
|
||||
|
||||
baseTypes.Add(name, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Type and Generic Parameter implementation cache
|
||||
|
||||
// cache for GetImplementationsOf
|
||||
internal static readonly Dictionary<string, HashSet<Type>> typeInheritance = new Dictionary<string, HashSet<Type>>();
|
||||
internal static readonly Dictionary<string, HashSet<Type>> genericParameterInheritance = new Dictionary<string, HashSet<Type>>();
|
||||
|
||||
public static string GetImplementationKey(Type type)
|
||||
{
|
||||
if (!type.IsGenericParameter)
|
||||
return type.FullName;
|
||||
else
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
sb.Append(type.GenericParameterAttributes)
|
||||
.Append('|');
|
||||
foreach (var c in type.GetGenericParameterConstraints())
|
||||
sb.Append(c.FullName).Append(',');
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get all non-abstract implementations of the provided type (include itself, if not abstract) in the current AppDomain.
|
||||
/// Also works for generic parameters by analyzing the constraints.
|
||||
/// </summary>
|
||||
/// <param name="baseType">The base type, which can optionally be abstract / interface.</param>
|
||||
/// <returns>All implementations of the type in the current AppDomain.</returns>
|
||||
public static HashSet<Type> GetImplementationsOf(Type baseType, bool allowAbstract, bool allowGeneric, bool allowRecursive = true)
|
||||
{
|
||||
var key = GetImplementationKey(baseType);
|
||||
|
||||
int count = AllTypes.Count;
|
||||
HashSet<Type> ret;
|
||||
if (!baseType.IsGenericParameter)
|
||||
ret = GetImplementations(key, baseType, allowAbstract, allowGeneric);
|
||||
else
|
||||
ret = GetGenericParameterImplementations(key, baseType, allowAbstract, allowGeneric);
|
||||
|
||||
// types were resolved during the parse, do it again if we're not already rebuilding.
|
||||
if (allowRecursive && AllTypes.Count != count)
|
||||
{
|
||||
ret = GetImplementationsOf(baseType, allowAbstract, allowGeneric, false);
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static HashSet<Type> GetImplementations(string key, Type baseType, bool allowAbstract, bool allowGeneric)
|
||||
{
|
||||
if (!typeInheritance.ContainsKey(key))
|
||||
{
|
||||
var set = new HashSet<Type>();
|
||||
var names = GetTypeNameArray();
|
||||
for (int i = 0; i < names.Length; i++)
|
||||
{
|
||||
var name = names[i];
|
||||
try
|
||||
{
|
||||
var type = AllTypes[name];
|
||||
|
||||
if (set.Contains(type)
|
||||
|| (type.IsAbstract && type.IsSealed) // ignore static classes
|
||||
|| (!allowAbstract && type.IsAbstract)
|
||||
|| (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition)))
|
||||
continue;
|
||||
|
||||
if (type.FullName.Contains("PrivateImplementationDetails")
|
||||
|| type.FullName.Contains("DisplayClass")
|
||||
|| type.FullName.Contains('<'))
|
||||
continue;
|
||||
|
||||
if (baseType.IsAssignableFrom(type) && !set.Contains(type))
|
||||
set.Add(type);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
//set.
|
||||
|
||||
typeInheritance.Add(key, set);
|
||||
}
|
||||
|
||||
return typeInheritance[key];
|
||||
}
|
||||
|
||||
private static HashSet<Type> GetGenericParameterImplementations(string key, Type baseType, bool allowAbstract, bool allowGeneric)
|
||||
{
|
||||
if (!genericParameterInheritance.ContainsKey(key))
|
||||
{
|
||||
var set = new HashSet<Type>();
|
||||
|
||||
var names = GetTypeNameArray();
|
||||
for (int i = 0; i < names.Length; i++)
|
||||
{
|
||||
var name = names[i];
|
||||
try
|
||||
{
|
||||
var type = AllTypes[name];
|
||||
|
||||
if (set.Contains(type)
|
||||
|| (type.IsAbstract && type.IsSealed) // ignore static classes
|
||||
|| (!allowAbstract && type.IsAbstract)
|
||||
|| (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition)))
|
||||
continue;
|
||||
|
||||
if (type.FullName.Contains("PrivateImplementationDetails")
|
||||
|| type.FullName.Contains("DisplayClass")
|
||||
|| type.FullName.Contains('<'))
|
||||
continue;
|
||||
|
||||
if (baseType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint)
|
||||
&& type.IsClass)
|
||||
continue;
|
||||
|
||||
if (baseType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint)
|
||||
&& type.IsValueType)
|
||||
continue;
|
||||
|
||||
if (baseType.GetGenericParameterConstraints().Any(it => !it.IsAssignableFrom(type)))
|
||||
continue;
|
||||
|
||||
set.Add(type);
|
||||
}
|
||||
catch { }
|
||||
}
|
||||
|
||||
genericParameterInheritance.Add(key, set);
|
||||
}
|
||||
|
||||
return genericParameterInheritance[key];
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Internal MemberInfo Cache
|
||||
|
||||
internal static Dictionary<Type, Dictionary<string, FieldInfo>> fieldInfos = new Dictionary<Type, Dictionary<string, FieldInfo>>();
|
||||
|
||||
public static FieldInfo GetFieldInfo(Type type, string fieldName)
|
||||
{
|
||||
if (!fieldInfos.ContainsKey(type))
|
||||
fieldInfos.Add(type, new Dictionary<string, FieldInfo>());
|
||||
|
||||
if (!fieldInfos[type].ContainsKey(fieldName))
|
||||
fieldInfos[type].Add(fieldName, type.GetField(fieldName, FLAGS));
|
||||
|
||||
return fieldInfos[type][fieldName];
|
||||
}
|
||||
|
||||
internal static Dictionary<Type, Dictionary<string, PropertyInfo>> propertyInfos = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
|
||||
|
||||
public static PropertyInfo GetPropertyInfo(Type type, string propertyName)
|
||||
{
|
||||
if (!propertyInfos.ContainsKey(type))
|
||||
propertyInfos.Add(type, new Dictionary<string, PropertyInfo>());
|
||||
|
||||
if (!propertyInfos[type].ContainsKey(propertyName))
|
||||
propertyInfos[type].Add(propertyName, type.GetProperty(propertyName, FLAGS));
|
||||
|
||||
return propertyInfos[type][propertyName];
|
||||
}
|
||||
|
||||
internal static Dictionary<Type, Dictionary<string, MethodInfo>> methodInfos = new Dictionary<Type, Dictionary<string, MethodInfo>>();
|
||||
|
||||
public static MethodInfo GetMethodInfo(Type type, string methodName)
|
||||
=> GetMethodInfo(type, methodName, ArgumentUtility.EmptyTypes, false);
|
||||
|
||||
public static MethodInfo GetMethodInfo(Type type, string methodName, Type[] argumentTypes, bool cacheAmbiguous = false)
|
||||
{
|
||||
if (!methodInfos.ContainsKey(type))
|
||||
methodInfos.Add(type, new Dictionary<string, MethodInfo>());
|
||||
|
||||
if (cacheAmbiguous)
|
||||
{
|
||||
methodName += "|";
|
||||
foreach (var arg in argumentTypes)
|
||||
methodName += arg.FullName + ",";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (!methodInfos[type].ContainsKey(methodName))
|
||||
{
|
||||
if (argumentTypes != null)
|
||||
methodInfos[type].Add(methodName, type.GetMethod(methodName, FLAGS, null, argumentTypes, null));
|
||||
else
|
||||
methodInfos[type].Add(methodName, type.GetMethod(methodName, FLAGS));
|
||||
}
|
||||
|
||||
return methodInfos[type][methodName];
|
||||
}
|
||||
catch (AmbiguousMatchException)
|
||||
{
|
||||
ExplorerCore.LogWarning($"AmbiguousMatchException trying to get method '{methodName}'");
|
||||
return null;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning($"{e.GetType()} trying to get method '{methodName}': {e.Message}\r\n{e.StackTrace}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region Reflection Blacklist
|
||||
|
||||
public virtual string[] DefaultReflectionBlacklist => new string[0];
|
||||
|
||||
public static void LoadBlacklistString(string blacklist)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (string.IsNullOrEmpty(blacklist) && !Instance.DefaultReflectionBlacklist.Any())
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var sigs = blacklist.Split(';');
|
||||
foreach (var sig in sigs)
|
||||
{
|
||||
var s = sig.Trim();
|
||||
if (string.IsNullOrEmpty(s))
|
||||
continue;
|
||||
if (!currentBlacklist.Contains(s))
|
||||
currentBlacklist.Add(s);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception parsing blacklist string: {ex.ReflectionExToString()}");
|
||||
}
|
||||
|
||||
foreach (var sig in Instance.DefaultReflectionBlacklist)
|
||||
{
|
||||
if (!currentBlacklist.Contains(sig))
|
||||
currentBlacklist.Add(sig);
|
||||
}
|
||||
|
||||
Mono.CSharp.IL2CPP.Blacklist.SignatureBlacklist = currentBlacklist;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception setting up reflection blacklist: {ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
public static bool IsBlacklisted(MemberInfo member)
|
||||
{
|
||||
if (string.IsNullOrEmpty(member.DeclaringType?.Namespace))
|
||||
return false;
|
||||
|
||||
var sig = $"{member.DeclaringType.FullName}.{member.Name}";
|
||||
|
||||
return currentBlacklist.Contains(sig);
|
||||
}
|
||||
|
||||
private static readonly HashSet<string> currentBlacklist = new HashSet<string>();
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
// Temp fix for IL2CPP until interface support improves
|
||||
|
||||
// IsEnumerable
|
||||
|
||||
public static bool IsEnumerable(Type type) => Instance.Internal_IsEnumerable(type);
|
||||
|
||||
protected virtual bool Internal_IsEnumerable(Type type)
|
||||
{
|
||||
return typeof(IEnumerable).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
// TryGetEnumerator (list)
|
||||
|
||||
public static bool TryGetEnumerator(object list, out IEnumerator enumerator)
|
||||
=> Instance.Internal_TryGetEnumerator(list, out enumerator);
|
||||
|
||||
protected virtual bool Internal_TryGetEnumerator(object list, out IEnumerator enumerator)
|
||||
{
|
||||
enumerator = (list as IEnumerable).GetEnumerator();
|
||||
return true;
|
||||
}
|
||||
|
||||
// TryGetEntryType
|
||||
|
||||
public static bool TryGetEntryType(Type enumerableType, out Type type)
|
||||
=> Instance.Internal_TryGetEntryType(enumerableType, out type);
|
||||
|
||||
protected virtual bool Internal_TryGetEntryType(Type enumerableType, out Type type)
|
||||
{
|
||||
// Check for arrays
|
||||
if (enumerableType.IsArray)
|
||||
{
|
||||
type = enumerableType.GetElementType();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check for implementation of IEnumerable<T>, IList<T> or ICollection<T>
|
||||
foreach (var t in enumerableType.GetInterfaces())
|
||||
{
|
||||
if (t.IsGenericType)
|
||||
{
|
||||
var typeDef = t.GetGenericTypeDefinition();
|
||||
if (typeDef == typeof(IEnumerable<>) || typeDef == typeof(IList<>) || typeDef == typeof(ICollection<>))
|
||||
{
|
||||
type = t.GetGenericArguments()[0];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Unable to determine any generic element type, just use object.
|
||||
type = typeof(object);
|
||||
return false;
|
||||
}
|
||||
|
||||
// IsDictionary
|
||||
|
||||
public static bool IsDictionary(Type type) => Instance.Internal_IsDictionary(type);
|
||||
|
||||
protected virtual bool Internal_IsDictionary(Type type)
|
||||
{
|
||||
return typeof(IDictionary).IsAssignableFrom(type);
|
||||
}
|
||||
|
||||
// TryGetEnumerator (dictionary)
|
||||
|
||||
public static bool TryGetDictEnumerator(object dictionary, out IEnumerator<DictionaryEntry> dictEnumerator)
|
||||
=> Instance.Internal_TryGetDictEnumerator(dictionary, out dictEnumerator);
|
||||
|
||||
protected virtual bool Internal_TryGetDictEnumerator(object dictionary, out IEnumerator<DictionaryEntry> dictEnumerator)
|
||||
{
|
||||
dictEnumerator = EnumerateDictionary((IDictionary)dictionary);
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerator<DictionaryEntry> EnumerateDictionary(IDictionary dict)
|
||||
{
|
||||
var enumerator = dict.GetEnumerator();
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
yield return new DictionaryEntry(enumerator.Key, enumerator.Value);
|
||||
}
|
||||
}
|
||||
|
||||
// TryGetEntryTypes
|
||||
|
||||
public static bool TryGetEntryTypes(Type dictionaryType, out Type keys, out Type values)
|
||||
=> Instance.Internal_TryGetEntryTypes(dictionaryType, out keys, out values);
|
||||
|
||||
protected virtual bool Internal_TryGetEntryTypes(Type dictionaryType, out Type keys, out Type values)
|
||||
{
|
||||
foreach (var t in dictionaryType.GetInterfaces())
|
||||
{
|
||||
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IDictionary<,>))
|
||||
{
|
||||
var args = t.GetGenericArguments();
|
||||
keys = args[0];
|
||||
values = args[1];
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
keys = typeof(object);
|
||||
values = typeof(object);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
#if CPP
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core.Runtime.Il2Cpp;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public class AssetBundle
|
||||
{
|
||||
// ~~~~~~~~~~~~ Static ~~~~~~~~~~~~
|
||||
|
||||
internal delegate IntPtr d_LoadFromFile(IntPtr path, uint crc, ulong offset);
|
||||
|
||||
public static AssetBundle LoadFromFile(string path)
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_LoadFromFile>("UnityEngine.AssetBundle::LoadFromFile_Internal");
|
||||
var ptr = iCall.Invoke(IL2CPP.ManagedStringToIl2Cpp(path), 0u, 0UL);
|
||||
return new AssetBundle(ptr);
|
||||
}
|
||||
|
||||
private delegate IntPtr d_LoadFromMemory(IntPtr binary, uint crc);
|
||||
|
||||
public static AssetBundle LoadFromMemory(byte[] binary, uint crc = 0)
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_LoadFromMemory>("UnityEngine.AssetBundle::LoadFromMemory_Internal");
|
||||
var ptr = iCall(((Il2CppStructArray<byte>) binary).Pointer, crc);
|
||||
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;
|
||||
|
||||
public AssetBundle(IntPtr ptr) { m_bundlePtr = ptr; }
|
||||
|
||||
// LoadAllAssets()
|
||||
|
||||
internal delegate IntPtr d_LoadAssetWithSubAssets_Internal(IntPtr _this, IntPtr name, IntPtr type);
|
||||
|
||||
public UnityEngine.Object[] LoadAllAssets()
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_LoadAssetWithSubAssets_Internal>("UnityEngine.AssetBundle::LoadAssetWithSubAssets_Internal");
|
||||
var ptr = iCall.Invoke(m_bundlePtr, IL2CPP.ManagedStringToIl2Cpp(""), Il2CppType.Of<UnityEngine.Object>().Pointer);
|
||||
|
||||
if (ptr == IntPtr.Zero)
|
||||
return new UnityEngine.Object[0];
|
||||
|
||||
return new Il2CppReferenceArray<UnityEngine.Object>(ptr);
|
||||
}
|
||||
|
||||
// LoadAsset<T>(string name, Type type)
|
||||
|
||||
internal delegate IntPtr d_LoadAsset_Internal(IntPtr _this, IntPtr name, IntPtr type);
|
||||
|
||||
public T LoadAsset<T>(string name) where T : UnityEngine.Object
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_LoadAsset_Internal>("UnityEngine.AssetBundle::LoadAsset_Internal");
|
||||
var ptr = iCall.Invoke(m_bundlePtr, IL2CPP.ManagedStringToIl2Cpp(name), Il2CppType.Of<T>().Pointer);
|
||||
|
||||
if (ptr == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
return new UnityEngine.Object(ptr).TryCast<T>();
|
||||
}
|
||||
|
||||
// public extern void Unload(bool unloadAllLoadedObjects);
|
||||
internal delegate void d_Unload(IntPtr _this, bool unloadAllLoadedObjects);
|
||||
|
||||
public void Unload(bool unloadAssets = true)
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_Unload>("UnityEngine.AssetBundle::Unload");
|
||||
iCall.Invoke(this.m_bundlePtr, unloadAssets);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,72 +0,0 @@
|
||||
#if CPP
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Linq;
|
||||
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)]
|
||||
public static extern IntPtr il2cpp_resolve_icall([MarshalAs(UnmanagedType.LPStr)] string name);
|
||||
|
||||
private static readonly Dictionary<string, Delegate> iCallCache = new Dictionary<string, Delegate>();
|
||||
|
||||
/// <summary>
|
||||
/// Helper to get and cache an iCall by providing the signature (eg. "UnityEngine.Resources::FindObjectsOfTypeAll").
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The Type of Delegate to provide for the iCall.</typeparam>
|
||||
/// <param name="signature">The signature of the iCall you want to get.</param>
|
||||
/// <returns>The <typeparamref name="T"/> delegate if successful.</returns>
|
||||
/// <exception cref="MissingMethodException">If the iCall could not be found.</exception>
|
||||
public static T GetICall<T>(string signature) where T : Delegate
|
||||
{
|
||||
if (iCallCache.ContainsKey(signature))
|
||||
return (T)iCallCache[signature];
|
||||
|
||||
IntPtr ptr = il2cpp_resolve_icall(signature);
|
||||
|
||||
if (ptr == IntPtr.Zero)
|
||||
throw new MissingMethodException($"Could not find any iCall with the signature '{signature}'!");
|
||||
|
||||
Delegate iCall = Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
|
||||
iCallCache.Add(signature, iCall);
|
||||
|
||||
return (T)iCall;
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, Delegate> s_unreliableCache = new Dictionary<string, Delegate>();
|
||||
|
||||
/// <summary>
|
||||
/// Get an iCall which may be one of multiple different signatures (ie, it changed in different Unity versions).
|
||||
/// Each possible signature must have the same Type pattern, it can only vary by name.
|
||||
/// </summary>
|
||||
public static T GetICallUnreliable<T>(IEnumerable<string> possibleSignatures) where T : Delegate
|
||||
{
|
||||
// use the first possible signature as the 'key'.
|
||||
string key = possibleSignatures.First();
|
||||
|
||||
if (s_unreliableCache.ContainsKey(key))
|
||||
return (T)s_unreliableCache[key];
|
||||
|
||||
T iCall;
|
||||
IntPtr ptr;
|
||||
foreach (var sig in possibleSignatures)
|
||||
{
|
||||
ptr = il2cpp_resolve_icall(sig);
|
||||
if (ptr != IntPtr.Zero)
|
||||
{
|
||||
iCall = (T)Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
|
||||
s_unreliableCache.Add(key, iCall);
|
||||
return iCall;
|
||||
}
|
||||
}
|
||||
|
||||
throw new MissingMethodException($"Could not find any iCall from list of provided signatures starting with '{key}'!");
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,159 +0,0 @@
|
||||
#if CPP
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnhollowerBaseLib;
|
||||
using UnityEngine;
|
||||
|
||||
// CREDIT HerpDerpenstine
|
||||
// https://github.com/LavaGang/MelonLoader/blob/master/MelonLoader.Support.Il2Cpp/MelonCoroutines.cs
|
||||
|
||||
namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
{
|
||||
public static class Il2CppCoroutine
|
||||
{
|
||||
private struct CoroTuple
|
||||
{
|
||||
public object WaitCondition;
|
||||
public IEnumerator Coroutine;
|
||||
}
|
||||
private static readonly List<CoroTuple> ourCoroutinesStore = new List<CoroTuple>();
|
||||
private static readonly List<IEnumerator> ourNextFrameCoroutines = new List<IEnumerator>();
|
||||
private static readonly List<IEnumerator> ourWaitForFixedUpdateCoroutines = new List<IEnumerator>();
|
||||
private static readonly List<IEnumerator> ourWaitForEndOfFrameCoroutines = new List<IEnumerator>();
|
||||
|
||||
private static readonly List<IEnumerator> tempList = new List<IEnumerator>();
|
||||
|
||||
internal static object Start(IEnumerator routine)
|
||||
{
|
||||
if (routine != null) ProcessNextOfCoroutine(routine);
|
||||
return routine;
|
||||
}
|
||||
|
||||
internal static void Stop(IEnumerator enumerator)
|
||||
{
|
||||
if (ourNextFrameCoroutines.Contains(enumerator)) // the coroutine is running itself
|
||||
ourNextFrameCoroutines.Remove(enumerator);
|
||||
else
|
||||
{
|
||||
int coroTupleIndex = ourCoroutinesStore.FindIndex(c => c.Coroutine == enumerator);
|
||||
if (coroTupleIndex != -1) // the coroutine is waiting for a subroutine
|
||||
{
|
||||
object waitCondition = ourCoroutinesStore[coroTupleIndex].WaitCondition;
|
||||
if (waitCondition is IEnumerator waitEnumerator)
|
||||
Stop(waitEnumerator);
|
||||
|
||||
ourCoroutinesStore.RemoveAt(coroTupleIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void ProcessCoroList(List<IEnumerator> target)
|
||||
{
|
||||
if (target.Count == 0) return;
|
||||
|
||||
// use a temp list to make sure waits made during processing are not handled by same processing invocation
|
||||
// additionally, a temp list reduces allocations compared to an array
|
||||
tempList.AddRange(target);
|
||||
target.Clear();
|
||||
foreach (var enumerator in tempList) ProcessNextOfCoroutine(enumerator);
|
||||
tempList.Clear();
|
||||
}
|
||||
|
||||
internal static void Process()
|
||||
{
|
||||
for (var i = ourCoroutinesStore.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var tuple = ourCoroutinesStore[i];
|
||||
if (tuple.WaitCondition is WaitForSeconds waitForSeconds)
|
||||
{
|
||||
if ((waitForSeconds.m_Seconds -= Time.deltaTime) <= 0)
|
||||
{
|
||||
ourCoroutinesStore.RemoveAt(i);
|
||||
ProcessNextOfCoroutine(tuple.Coroutine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ProcessCoroList(ourNextFrameCoroutines);
|
||||
}
|
||||
|
||||
internal static void ProcessWaitForFixedUpdate() => ProcessCoroList(ourWaitForFixedUpdateCoroutines);
|
||||
|
||||
internal static void ProcessWaitForEndOfFrame() => ProcessCoroList(ourWaitForEndOfFrameCoroutines);
|
||||
|
||||
private static void ProcessNextOfCoroutine(IEnumerator enumerator)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!enumerator.MoveNext()) // Run the next step of the coroutine. If it's done, restore the parent routine
|
||||
{
|
||||
var indices = ourCoroutinesStore.Select((it, idx) => (idx, it)).Where(it => it.it.WaitCondition == enumerator).Select(it => it.idx).ToList();
|
||||
for (var i = indices.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var index = indices[i];
|
||||
ourNextFrameCoroutines.Add(ourCoroutinesStore[index].Coroutine);
|
||||
ourCoroutinesStore.RemoveAt(index);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogError(e.ToString());
|
||||
Stop(FindOriginalCoro(enumerator)); // We want the entire coroutine hierachy to stop when an error happen
|
||||
}
|
||||
|
||||
var next = enumerator.Current;
|
||||
switch (next)
|
||||
{
|
||||
case null:
|
||||
ourNextFrameCoroutines.Add(enumerator);
|
||||
return;
|
||||
case WaitForFixedUpdate _:
|
||||
ourWaitForFixedUpdateCoroutines.Add(enumerator);
|
||||
return;
|
||||
case WaitForEndOfFrame _:
|
||||
ourWaitForEndOfFrameCoroutines.Add(enumerator);
|
||||
return;
|
||||
case WaitForSeconds _:
|
||||
break; // do nothing, this one is supported in Process
|
||||
case Il2CppObjectBase il2CppObjectBase:
|
||||
var nextAsEnumerator = il2CppObjectBase.TryCast<Il2CppSystem.Collections.IEnumerator>();
|
||||
if (nextAsEnumerator != null) // il2cpp IEnumerator also handles CustomYieldInstruction
|
||||
next = new Il2CppEnumeratorWrapper(nextAsEnumerator);
|
||||
else
|
||||
ExplorerCore.LogWarning($"Unknown coroutine yield object of type '{il2CppObjectBase}' for coroutine '{enumerator}'");
|
||||
return;
|
||||
default:
|
||||
ExplorerCore.LogWarning($"Unknown coroutine yield object of type '{next}' for coroutine '{enumerator}'");
|
||||
return;
|
||||
}
|
||||
|
||||
ourCoroutinesStore.Add(new CoroTuple { WaitCondition = next, Coroutine = enumerator });
|
||||
|
||||
if (next is IEnumerator nextCoro)
|
||||
ProcessNextOfCoroutine(nextCoro);
|
||||
}
|
||||
|
||||
private static IEnumerator FindOriginalCoro(IEnumerator enumerator)
|
||||
{
|
||||
int index = ourCoroutinesStore.FindIndex(ct => ct.WaitCondition == enumerator);
|
||||
if (index == -1)
|
||||
return enumerator;
|
||||
return FindOriginalCoro(ourCoroutinesStore[index].Coroutine);
|
||||
}
|
||||
|
||||
private class Il2CppEnumeratorWrapper : IEnumerator
|
||||
{
|
||||
private readonly Il2CppSystem.Collections.IEnumerator il2cppEnumerator;
|
||||
|
||||
public Il2CppEnumeratorWrapper(Il2CppSystem.Collections.IEnumerator il2CppEnumerator) => il2cppEnumerator = il2CppEnumerator;
|
||||
public bool MoveNext() => il2cppEnumerator.MoveNext();
|
||||
public void Reset() => il2cppEnumerator.Reset();
|
||||
public object Current => il2cppEnumerator.Current;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,270 +0,0 @@
|
||||
#if CPP
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BF = System.Reflection.BindingFlags;
|
||||
using System.Text;
|
||||
using UnhollowerBaseLib;
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.SceneManagement;
|
||||
using System.Collections;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
{
|
||||
public class Il2CppProvider : RuntimeProvider
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
ExplorerCore.Context = RuntimeContext.IL2CPP;
|
||||
TextureUtil = new Il2CppTextureUtil();
|
||||
}
|
||||
|
||||
public override void SetupEvents()
|
||||
{
|
||||
try
|
||||
{
|
||||
Application.add_logMessageReceived(new Action<string, string, LogType>(Application_logMessageReceived));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning("Exception setting up Unity log listener, make sure Unity libraries have been unstripped!");
|
||||
ExplorerCore.Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
|
||||
{
|
||||
ExplorerCore.LogUnity(condition, type);
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
Il2CppCoroutine.Process();
|
||||
}
|
||||
|
||||
internal override void ProcessOnPostRender()
|
||||
{
|
||||
Il2CppCoroutine.ProcessWaitForEndOfFrame();
|
||||
}
|
||||
|
||||
internal override void ProcessFixedUpdate()
|
||||
{
|
||||
Il2CppCoroutine.ProcessWaitForFixedUpdate();
|
||||
}
|
||||
|
||||
public override void StartCoroutine(IEnumerator routine)
|
||||
{
|
||||
Il2CppCoroutine.Start(routine);
|
||||
}
|
||||
|
||||
public override T AddComponent<T>(GameObject obj, Type type)
|
||||
{
|
||||
return obj.AddComponent(Il2CppType.From(type)).TryCast<T>();
|
||||
}
|
||||
|
||||
public override ScriptableObject CreateScriptable(Type type)
|
||||
{
|
||||
return ScriptableObject.CreateInstance(Il2CppType.From(type));
|
||||
}
|
||||
|
||||
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
|
||||
{
|
||||
var il2cppList = new Il2CppSystem.Collections.Generic.List<RaycastResult>();
|
||||
|
||||
raycaster.Raycast(data, il2cppList);
|
||||
|
||||
if (il2cppList.Count > 0)
|
||||
list.AddRange(il2cppList.ToArray());
|
||||
}
|
||||
|
||||
// LayerMask.LayerToName
|
||||
|
||||
internal delegate IntPtr d_LayerToName(int layer);
|
||||
|
||||
public override string LayerToName(int layer)
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_LayerToName>("UnityEngine.LayerMask::LayerToName");
|
||||
return IL2CPP.Il2CppStringToManaged(iCall.Invoke(layer));
|
||||
}
|
||||
|
||||
// Resources.FindObjectsOfTypeAll
|
||||
|
||||
internal delegate IntPtr d_FindObjectsOfTypeAll(IntPtr type);
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
// Scene.GetRootGameObjects();
|
||||
|
||||
internal delegate void d_GetRootGameObjects(int handle, IntPtr list);
|
||||
|
||||
public override GameObject[] GetRootGameObjects(Scene scene)
|
||||
{
|
||||
if (!scene.isLoaded)
|
||||
return new GameObject[0];
|
||||
|
||||
int handle = scene.handle;
|
||||
|
||||
if (handle == -1)
|
||||
return new GameObject[0];
|
||||
|
||||
int count = GetRootCount(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);
|
||||
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
// Scene.rootCount
|
||||
|
||||
internal delegate int d_GetRootCountInternal(int handle);
|
||||
|
||||
public override int GetRootCount(Scene scene) => GetRootCount(scene.handle);
|
||||
|
||||
public static int GetRootCount(int handle)
|
||||
{
|
||||
return ICallManager.GetICall<d_GetRootCountInternal>("UnityEngine.SceneManagement.Scene::GetRootCountInternal")
|
||||
.Invoke(handle);
|
||||
}
|
||||
|
||||
internal static bool triedToGetColorBlockProps;
|
||||
internal static PropertyInfo _normalColorProp;
|
||||
internal static PropertyInfo _highlightColorProp;
|
||||
internal static PropertyInfo _pressedColorProp;
|
||||
internal static PropertyInfo _disabledColorProp;
|
||||
|
||||
public override void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
|
||||
Color? disabled = null)
|
||||
{
|
||||
var colors = selectable.colors;
|
||||
|
||||
colors.colorMultiplier = 1;
|
||||
|
||||
object boxed = (object)colors;
|
||||
|
||||
if (!triedToGetColorBlockProps)
|
||||
{
|
||||
triedToGetColorBlockProps = true;
|
||||
|
||||
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "normalColor") is PropertyInfo norm && norm.CanWrite)
|
||||
_normalColorProp = norm;
|
||||
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "highlightedColor") is PropertyInfo high && high.CanWrite)
|
||||
_highlightColorProp = high;
|
||||
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "pressedColor") is PropertyInfo pres && pres.CanWrite)
|
||||
_pressedColorProp = pres;
|
||||
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "disabledColor") is PropertyInfo disa && disa.CanWrite)
|
||||
_disabledColorProp = disa;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (normal != null)
|
||||
{
|
||||
if (_normalColorProp != null)
|
||||
_normalColorProp.SetValue(boxed, (Color)normal);
|
||||
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_NormalColor") is FieldInfo fi)
|
||||
fi.SetValue(boxed, (Color)normal);
|
||||
}
|
||||
|
||||
if (highlighted != null)
|
||||
{
|
||||
if (_highlightColorProp != null)
|
||||
_highlightColorProp.SetValue(boxed, (Color)highlighted);
|
||||
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_HighlightedColor") is FieldInfo fi)
|
||||
fi.SetValue(boxed, (Color)highlighted);
|
||||
}
|
||||
|
||||
if (pressed != null)
|
||||
{
|
||||
if (_pressedColorProp != null)
|
||||
_pressedColorProp.SetValue(boxed, (Color)pressed);
|
||||
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_PressedColor") is FieldInfo fi)
|
||||
fi.SetValue(boxed, (Color)pressed);
|
||||
}
|
||||
|
||||
if (disabled != null)
|
||||
{
|
||||
if (_disabledColorProp != null)
|
||||
_disabledColorProp.SetValue(boxed, (Color)disabled);
|
||||
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_DisabledColor") is FieldInfo fi)
|
||||
fi.SetValue(boxed, (Color)disabled);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log(ex);
|
||||
}
|
||||
|
||||
colors = (ColorBlock)boxed;
|
||||
|
||||
SetColorBlock(selectable, colors);
|
||||
}
|
||||
|
||||
public override void SetColorBlock(Selectable selectable, ColorBlock _colorBlock)
|
||||
{
|
||||
try
|
||||
{
|
||||
selectable = selectable.TryCast<Selectable>();
|
||||
|
||||
ReflectionUtility.GetPropertyInfo(typeof(Selectable), "m_Colors")
|
||||
.SetValue(selectable, _colorBlock, null);
|
||||
|
||||
ReflectionUtility.GetMethodInfo(typeof(Selectable), "OnSetProperty")
|
||||
.Invoke(selectable, ArgumentUtility.EmptyArgs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log(ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Il2CppExtensions
|
||||
{
|
||||
public static void AddListener(this UnityEvent action, Action listener)
|
||||
{
|
||||
action.AddListener(listener);
|
||||
}
|
||||
|
||||
public static void AddListener<T>(this UnityEvent<T> action, Action<T> listener)
|
||||
{
|
||||
action.AddListener(listener);
|
||||
}
|
||||
|
||||
public static void RemoveListener(this UnityEvent action, Action listener)
|
||||
{
|
||||
action.RemoveListener(listener);
|
||||
}
|
||||
|
||||
public static void RemoveListener<T>(this UnityEvent<T> action, Action<T> listener)
|
||||
{
|
||||
action.RemoveListener(listener);
|
||||
}
|
||||
|
||||
public static void SetChildControlHeight(this HorizontalOrVerticalLayoutGroup group, bool value) => group.childControlHeight = value;
|
||||
public static void SetChildControlWidth(this HorizontalOrVerticalLayoutGroup group, bool value) => group.childControlWidth = value;
|
||||
}
|
||||
|
||||
#endif
|
@ -1,77 +0,0 @@
|
||||
#if CPP
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using UnhollowerBaseLib;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
{
|
||||
public class Il2CppTextureUtil : TextureUtilProvider
|
||||
{
|
||||
public override Texture2D NewTexture2D(int width, int height)
|
||||
=> new Texture2D((int)width, (int)height, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero);
|
||||
|
||||
internal delegate void d_Blit2(IntPtr source, IntPtr dest);
|
||||
|
||||
public override void Blit(Texture2D tex, RenderTexture rt)
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_Blit2>("UnityEngine.Graphics::Blit2");
|
||||
iCall.Invoke(tex.Pointer, rt.Pointer);
|
||||
}
|
||||
|
||||
// byte[] ImageConversion.EncodeToPNG(this Texture2D image);
|
||||
|
||||
internal delegate IntPtr d_EncodeToPNG(IntPtr tex);
|
||||
|
||||
public override byte[] EncodeToPNG(Texture2D tex)
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_EncodeToPNG>("UnityEngine.ImageConversion::EncodeToPNG");
|
||||
|
||||
IntPtr ptr = iCall.Invoke(tex.Pointer);
|
||||
|
||||
if (ptr == IntPtr.Zero)
|
||||
return null;
|
||||
|
||||
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)
|
||||
{
|
||||
return CreateSpriteImpl(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero, 100f, 0u, Vector4.zero);
|
||||
}
|
||||
|
||||
internal delegate IntPtr d_CreateSprite(IntPtr texture, ref Rect rect, ref Vector2 pivot, float pixelsPerUnit,
|
||||
uint extrude, int meshType, ref Vector4 border, bool generateFallbackPhysicsShape);
|
||||
|
||||
public static Sprite CreateSpriteImpl(Texture texture, Rect rect, Vector2 pivot, float pixelsPerUnit, uint extrude, Vector4 border)
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_CreateSprite>("UnityEngine.Sprite::CreateSprite_Injected");
|
||||
|
||||
var ptr = iCall.Invoke(texture.Pointer, ref rect, ref pivot, pixelsPerUnit, extrude, 1, ref border, false);
|
||||
|
||||
if (ptr == IntPtr.Zero)
|
||||
return null;
|
||||
else
|
||||
return new Sprite(ptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,161 +0,0 @@
|
||||
#if MONO
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace UnityExplorer.Core.Runtime.Mono
|
||||
{
|
||||
public class MonoProvider : RuntimeProvider
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
ExplorerCore.Context = RuntimeContext.Mono;
|
||||
//Reflection = new MonoReflection();
|
||||
TextureUtil = new MonoTextureUtil();
|
||||
}
|
||||
|
||||
public override void SetupEvents()
|
||||
{
|
||||
Application.logMessageReceived += Application_logMessageReceived;
|
||||
}
|
||||
|
||||
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
|
||||
{
|
||||
ExplorerCore.LogUnity(condition, type);
|
||||
}
|
||||
|
||||
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 ScriptableObject CreateScriptable(Type type)
|
||||
{
|
||||
return ScriptableObject.CreateInstance(type);
|
||||
}
|
||||
|
||||
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
|
||||
{
|
||||
raycaster.Raycast(data, list);
|
||||
}
|
||||
|
||||
public override string LayerToName(int layer)
|
||||
=> LayerMask.LayerToName(layer);
|
||||
|
||||
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 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 void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
|
||||
Color? disabled = null)
|
||||
{
|
||||
var colors = selectable.colors;
|
||||
|
||||
if (normal != null)
|
||||
colors.normalColor = (Color)normal;
|
||||
|
||||
if (highlighted != null)
|
||||
colors.highlightedColor = (Color)highlighted;
|
||||
|
||||
if (pressed != null)
|
||||
colors.pressedColor = (Color)pressed;
|
||||
|
||||
if (disabled != null)
|
||||
colors.disabledColor = (Color)disabled;
|
||||
|
||||
SetColorBlock(selectable, colors);
|
||||
}
|
||||
|
||||
public override void SetColorBlock(Selectable selectable, ColorBlock colors)
|
||||
{
|
||||
selectable.colors = colors;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class MonoExtensions
|
||||
{
|
||||
public static void AddListener(this UnityEvent _event, Action listener)
|
||||
{
|
||||
_event.AddListener(new UnityAction(listener));
|
||||
}
|
||||
|
||||
public static void AddListener<T>(this UnityEvent<T> _event, Action<T> listener)
|
||||
{
|
||||
_event.AddListener(new UnityAction<T>(listener));
|
||||
}
|
||||
|
||||
public static void RemoveListener(this UnityEvent _event, Action listener)
|
||||
{
|
||||
_event.RemoveListener(new UnityAction(listener));
|
||||
}
|
||||
|
||||
public static void RemoveListener<T>(this UnityEvent<T> _event, Action<T> listener)
|
||||
{
|
||||
_event.RemoveListener(new UnityAction<T>(listener));
|
||||
}
|
||||
|
||||
public static void Clear(this StringBuilder sb)
|
||||
{
|
||||
sb.Remove(0, sb.Length);
|
||||
}
|
||||
|
||||
private static PropertyInfo pi_childControlHeight;
|
||||
|
||||
public static void SetChildControlHeight(this HorizontalOrVerticalLayoutGroup group, bool value)
|
||||
{
|
||||
if (pi_childControlHeight == null)
|
||||
pi_childControlHeight = group.GetType().GetProperty("childControlHeight");
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
@ -1,66 +0,0 @@
|
||||
#if MONO
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core;
|
||||
|
||||
namespace UnityExplorer.Core.Runtime.Mono
|
||||
{
|
||||
public class MonoTextureUtil : TextureUtilProvider
|
||||
{
|
||||
public override void Blit(Texture2D tex, RenderTexture 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);
|
||||
}
|
||||
|
||||
public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
|
||||
{
|
||||
return tex.LoadImage(data, markNonReadable);
|
||||
}
|
||||
|
||||
public override Texture2D NewTexture2D(int width, int height)
|
||||
{
|
||||
return new Texture2D(width, height);
|
||||
}
|
||||
|
||||
public override byte[] EncodeToPNG(Texture2D tex)
|
||||
{
|
||||
return 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);
|
||||
}
|
||||
|
||||
private static MethodInfo GetEncodeToPNGMethod()
|
||||
{
|
||||
if (ReflectionUtility.GetTypeByName("UnityEngine.ImageConversion") is Type imageConversion)
|
||||
return m_encodeToPNGMethod = imageConversion.GetMethod("EncodeToPNG", ReflectionUtility.FLAGS);
|
||||
|
||||
var method = typeof(Texture2D).GetMethod("EncodeToPNG", ReflectionUtility.FLAGS);
|
||||
if (method != null)
|
||||
return m_encodeToPNGMethod = method;
|
||||
|
||||
ExplorerCore.Log("ERROR: Cannot get any EncodeToPNG method!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace UnityExplorer.Core.Runtime
|
||||
{
|
||||
public enum RuntimeContext
|
||||
{
|
||||
Mono,
|
||||
IL2CPP
|
||||
}
|
||||
}
|
@ -1,76 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
|
||||
// Intentionally project-wide namespace so that its always easily accessible.
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public abstract class RuntimeProvider
|
||||
{
|
||||
public static RuntimeProvider Instance;
|
||||
|
||||
public TextureUtilProvider TextureUtil;
|
||||
|
||||
public RuntimeProvider()
|
||||
{
|
||||
Initialize();
|
||||
|
||||
SetupEvents();
|
||||
}
|
||||
|
||||
public static void Init() =>
|
||||
#if CPP
|
||||
Instance = new Core.Runtime.Il2Cpp.Il2CppProvider();
|
||||
#else
|
||||
Instance = new Core.Runtime.Mono.MonoProvider();
|
||||
#endif
|
||||
|
||||
public abstract void Initialize();
|
||||
|
||||
public abstract void SetupEvents();
|
||||
|
||||
public abstract void StartCoroutine(IEnumerator routine);
|
||||
|
||||
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;
|
||||
|
||||
public abstract ScriptableObject CreateScriptable(Type type);
|
||||
|
||||
public abstract string LayerToName(int layer);
|
||||
|
||||
public abstract UnityEngine.Object[] FindObjectsOfTypeAll(Type type);
|
||||
|
||||
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);
|
||||
|
||||
public abstract void SetColorBlock(Selectable selectable, ColorBlock colors);
|
||||
|
||||
public abstract void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
|
||||
Color? disabled = null);
|
||||
|
||||
internal virtual void ProcessOnPostRender()
|
||||
{
|
||||
}
|
||||
|
||||
internal virtual void ProcessFixedUpdate()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@ -1,160 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.Core.Runtime
|
||||
{
|
||||
public abstract class TextureUtilProvider
|
||||
{
|
||||
public static TextureUtilProvider Instance;
|
||||
|
||||
public TextureUtilProvider()
|
||||
{
|
||||
Instance = this;
|
||||
}
|
||||
|
||||
public abstract byte[] EncodeToPNG(Texture2D tex);
|
||||
|
||||
public abstract Texture2D NewTexture2D(int width, int height);
|
||||
|
||||
public abstract void Blit(Texture2D tex, RenderTexture rt);
|
||||
|
||||
public abstract bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable);
|
||||
|
||||
public abstract Sprite CreateSprite(Texture2D texture);
|
||||
|
||||
public static bool IsReadable(Texture2D tex)
|
||||
{
|
||||
try
|
||||
{
|
||||
// This will cause an exception if it's not readable.
|
||||
// Reason for doing it this way is not all Unity versions
|
||||
// ship with the 'Texture.isReadable' property.
|
||||
|
||||
tex.GetPixel(0, 0);
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
Texture2D newTex = Instance.NewTexture2D((int)rect.width, (int)rect.height);
|
||||
|
||||
newTex.SetPixels(pixels);
|
||||
|
||||
return newTex;
|
||||
}
|
||||
|
||||
public static Texture2D ForceReadTexture(Texture2D tex)
|
||||
{
|
||||
try
|
||||
{
|
||||
FilterMode origFilter = tex.filterMode;
|
||||
tex.filterMode = FilterMode.Point;
|
||||
|
||||
var rt = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.ARGB32);
|
||||
rt.filterMode = FilterMode.Point;
|
||||
RenderTexture.active = rt;
|
||||
|
||||
Instance.Blit(tex, rt);
|
||||
|
||||
var _newTex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false);
|
||||
|
||||
_newTex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0);
|
||||
_newTex.Apply(false, false);
|
||||
|
||||
RenderTexture.active = null;
|
||||
tex.filterMode = origFilter;
|
||||
|
||||
return _newTex;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log("Exception on ForceReadTexture: " + e.ToString());
|
||||
return default;
|
||||
}
|
||||
}
|
||||
|
||||
public static void SaveTextureAsPNG(Texture2D tex, string dir, string name, bool isDTXnmNormal = false)
|
||||
{
|
||||
if (!Directory.Exists(dir))
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
byte[] data;
|
||||
string savepath = dir + @"\" + name + ".png";
|
||||
|
||||
// Make sure we can EncodeToPNG it.
|
||||
if (tex.format != TextureFormat.ARGB32 || !IsReadable(tex))
|
||||
{
|
||||
tex = ForceReadTexture(tex);
|
||||
}
|
||||
|
||||
if (isDTXnmNormal)
|
||||
{
|
||||
tex = DTXnmToRGBA(tex);
|
||||
tex.Apply(false, false);
|
||||
}
|
||||
|
||||
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.
|
||||
public static Texture2D DTXnmToRGBA(Texture2D tex)
|
||||
{
|
||||
Color[] colors = tex.GetPixels();
|
||||
|
||||
for (int i = 0; i < colors.Length; i++)
|
||||
{
|
||||
var c = colors[i];
|
||||
|
||||
c.r = c.a * 2 - 1; // red <- alpha
|
||||
c.g = c.g * 2 - 1; // green is always the same
|
||||
|
||||
var rg = new Vector2(c.r, c.g); //this is the red-green vector
|
||||
c.b = Mathf.Sqrt(1 - Mathf.Clamp01(Vector2.Dot(rg, rg))); //recalculate the blue channel
|
||||
|
||||
colors[i] = new Color(
|
||||
(c.r * 0.5f) + 0.5f,
|
||||
(c.g * 0.5f) + 0.25f,
|
||||
(c.b * 0.5f) + 0.5f
|
||||
);
|
||||
}
|
||||
|
||||
var newtex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false);
|
||||
newtex.SetPixels(colors);
|
||||
|
||||
return newtex;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public static class ArgumentUtility
|
||||
{
|
||||
public static readonly Type[] EmptyTypes = new Type[0];
|
||||
public static readonly object[] EmptyArgs = new object[0];
|
||||
|
||||
public static readonly Type[] ParseArgs = new Type[] { typeof(string) };
|
||||
}
|
||||
}
|
@ -1,29 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public static class IOUtility
|
||||
{
|
||||
private static readonly char[] invalidDirectoryCharacters = Path.GetInvalidPathChars();
|
||||
private static readonly char[] invalidFilenameCharacters = Path.GetInvalidFileNameChars();
|
||||
|
||||
public static string EnsureValidDirectory(string path)
|
||||
{
|
||||
path = string.Concat(path.Split(invalidDirectoryCharacters));
|
||||
|
||||
if (!Directory.Exists(path))
|
||||
Directory.CreateDirectory(path);
|
||||
|
||||
return path;
|
||||
}
|
||||
|
||||
public static string EnsureValidFilename(string filename)
|
||||
{
|
||||
return string.Concat(filename.Split(invalidFilenameCharacters));
|
||||
}
|
||||
}
|
||||
}
|
@ -1,28 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public static class MiscUtility
|
||||
{
|
||||
/// <summary>
|
||||
/// Check if a string contains another string, case-insensitive.
|
||||
/// </summary>
|
||||
public static bool ContainsIgnoreCase(this string _this, string s)
|
||||
{
|
||||
return ParseUtility.en_US.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Just to allow Enum to do .HasFlag() in NET 3.5
|
||||
/// </summary>
|
||||
public static bool HasFlag(this Enum flags, Enum value)
|
||||
{
|
||||
ulong flag = Convert.ToUInt64(value);
|
||||
return (Convert.ToUInt64(flags) & flag) == flag;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,420 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
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),
|
||||
typeof(decimal),
|
||||
typeof(DateTime),
|
||||
};
|
||||
|
||||
public const string NUMBER_FORMAT = "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);
|
||||
}
|
||||
|
||||
public static string GetSequenceFormatString(int count)
|
||||
{
|
||||
if (count <= 0)
|
||||
return null;
|
||||
|
||||
if (numSequenceStrings.ContainsKey(count))
|
||||
return numSequenceStrings[count];
|
||||
|
||||
string[] strings = new string[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
strings[i] = $"{{{i}:{NUMBER_FORMAT}}}";
|
||||
|
||||
string s = string.Join(", ", strings);
|
||||
|
||||
numSequenceStrings.Add(count, s);
|
||||
return s;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public static bool TryParse(string input, Type type, out object obj, out Exception parseException)
|
||||
{
|
||||
obj = null;
|
||||
parseException = null;
|
||||
|
||||
if (type == null)
|
||||
return false;
|
||||
|
||||
if (type == typeof(string))
|
||||
{
|
||||
obj = input;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
try
|
||||
{
|
||||
obj = Enum.Parse(type, input);
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
parseException = ex.GetInnerMostException();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (customTypes.ContainsKey(type.FullName))
|
||||
{
|
||||
obj = customTypes[type.FullName].Invoke(input);
|
||||
}
|
||||
else
|
||||
{
|
||||
obj = ReflectionUtility.GetMethodInfo(type, "Parse", ArgumentUtility.ParseArgs)
|
||||
.Invoke(null, new object[] { input });
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ex = ex.GetInnerMostException();
|
||||
parseException = ex;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static readonly HashSet<Type> formattedTypes = new HashSet<Type>
|
||||
{
|
||||
typeof(float),
|
||||
typeof(double),
|
||||
typeof(decimal)
|
||||
};
|
||||
|
||||
public static string ToStringForInput(object obj, Type type)
|
||||
{
|
||||
if (type == null || obj == null)
|
||||
return null;
|
||||
|
||||
if (type == typeof(string))
|
||||
return obj as string;
|
||||
|
||||
if (type.IsEnum)
|
||||
{
|
||||
return Enum.IsDefined(type, obj)
|
||||
? Enum.GetName(type, obj)
|
||||
: obj.ToString();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
if (customTypes.ContainsKey(type.FullName))
|
||||
{
|
||||
return customTypesToString[type.FullName].Invoke(obj);
|
||||
}
|
||||
else if (formattedTypes.Contains(type))
|
||||
{
|
||||
return ReflectionUtility.GetMethodInfo(type, "ToString", new Type[] { typeof(string), typeof(IFormatProvider) })
|
||||
.Invoke(obj, new object[] { NUMBER_FORMAT, en_US })
|
||||
as string;
|
||||
}
|
||||
else
|
||||
return obj.ToString();
|
||||
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception formatting object for input: {ex}");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, string> typeInputExamples = new Dictionary<string, string>();
|
||||
|
||||
public static string GetExampleInput(Type type)
|
||||
{
|
||||
if (!typeInputExamples.ContainsKey(type.AssemblyQualifiedName))
|
||||
{
|
||||
try
|
||||
{
|
||||
if (type.IsEnum)
|
||||
{
|
||||
typeInputExamples.Add(type.AssemblyQualifiedName, Enum.GetNames(type).First());
|
||||
}
|
||||
else
|
||||
{
|
||||
var instance = Activator.CreateInstance(type);
|
||||
typeInputExamples.Add(type.AssemblyQualifiedName, ToStringForInput(instance, type));
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning("Exception generating default instance for example input for '" + type.FullName + "'");
|
||||
ExplorerCore.Log(ex);
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
return typeInputExamples[type.AssemblyQualifiedName];
|
||||
}
|
||||
|
||||
#region Custom parse methods
|
||||
|
||||
internal delegate object ParseMethod(string input);
|
||||
|
||||
private static readonly Dictionary<string, ParseMethod> customTypes = new Dictionary<string, ParseMethod>
|
||||
{
|
||||
{ typeof(Vector2).FullName, TryParseVector2 },
|
||||
{ typeof(Vector3).FullName, TryParseVector3 },
|
||||
{ typeof(Vector4).FullName, TryParseVector4 },
|
||||
{ typeof(Quaternion).FullName, TryParseQuaternion },
|
||||
{ typeof(Rect).FullName, TryParseRect },
|
||||
{ typeof(Color).FullName, TryParseColor },
|
||||
{ typeof(Color32).FullName, TryParseColor32 },
|
||||
{ typeof(LayerMask).FullName, TryParseLayerMask },
|
||||
};
|
||||
|
||||
internal delegate string ToStringMethod(object obj);
|
||||
|
||||
private static readonly Dictionary<string, ToStringMethod> customTypesToString = new Dictionary<string, ToStringMethod>
|
||||
{
|
||||
{ typeof(Vector2).FullName, Vector2ToString },
|
||||
{ typeof(Vector3).FullName, Vector3ToString },
|
||||
{ typeof(Vector4).FullName, Vector4ToString },
|
||||
{ typeof(Quaternion).FullName, QuaternionToString },
|
||||
{ typeof(Rect).FullName, RectToString },
|
||||
{ typeof(Color).FullName, ColorToString },
|
||||
{ typeof(Color32).FullName, Color32ToString },
|
||||
{ typeof(LayerMask).FullName, LayerMaskToString },
|
||||
};
|
||||
|
||||
// Vector2
|
||||
|
||||
public static object TryParseVector2(string input)
|
||||
{
|
||||
Vector2 vector = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
|
||||
vector.x = float.Parse(split[0].Trim(), en_US);
|
||||
vector.y = float.Parse(split[1].Trim(), en_US);
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
public static string Vector2ToString(object obj)
|
||||
{
|
||||
if (!(obj is Vector2 vector))
|
||||
return null;
|
||||
|
||||
return FormatDecimalSequence(vector.x, vector.y);
|
||||
}
|
||||
|
||||
// Vector3
|
||||
|
||||
public static object TryParseVector3(string input)
|
||||
{
|
||||
Vector3 vector = default;
|
||||
|
||||
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);
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
public static string Vector3ToString(object obj)
|
||||
{
|
||||
if (!(obj is Vector3 vector))
|
||||
return null;
|
||||
|
||||
return FormatDecimalSequence(vector.x, vector.y, vector.z);
|
||||
}
|
||||
|
||||
// Vector4
|
||||
|
||||
public static object TryParseVector4(string input)
|
||||
{
|
||||
Vector4 vector = default;
|
||||
|
||||
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);
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
public static string Vector4ToString(object obj)
|
||||
{
|
||||
if (!(obj is Vector4 vector))
|
||||
return null;
|
||||
|
||||
return FormatDecimalSequence(vector.x, vector.y, vector.z, vector.w);
|
||||
}
|
||||
|
||||
// Quaternion
|
||||
|
||||
public static object TryParseQuaternion(string input)
|
||||
{
|
||||
Vector3 vector = default;
|
||||
|
||||
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);
|
||||
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);
|
||||
return Quaternion.Euler(vector);
|
||||
}
|
||||
}
|
||||
|
||||
public static string QuaternionToString(object obj)
|
||||
{
|
||||
if (!(obj is Quaternion quaternion))
|
||||
return null;
|
||||
|
||||
Vector3 vector = quaternion.eulerAngles;
|
||||
|
||||
return FormatDecimalSequence(vector.x, vector.y, vector.z);
|
||||
}
|
||||
|
||||
// Rect
|
||||
|
||||
public static object TryParseRect(string input)
|
||||
{
|
||||
Rect rect = default;
|
||||
|
||||
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);
|
||||
|
||||
return rect;
|
||||
}
|
||||
|
||||
public static string RectToString(object obj)
|
||||
{
|
||||
if (!(obj is Rect rect))
|
||||
return null;
|
||||
|
||||
return FormatDecimalSequence(rect.x, rect.y, rect.width, rect.height);
|
||||
}
|
||||
|
||||
// Color
|
||||
|
||||
public static object TryParseColor(string input)
|
||||
{
|
||||
Color color = default;
|
||||
|
||||
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);
|
||||
if (split.Length > 3)
|
||||
color.a = float.Parse(split[3].Trim(), en_US);
|
||||
else
|
||||
color.a = 1;
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
public static string ColorToString(object obj)
|
||||
{
|
||||
if (!(obj is Color color))
|
||||
return null;
|
||||
|
||||
return FormatDecimalSequence(color.r, color.g, color.b, color.a);
|
||||
}
|
||||
|
||||
// Color32
|
||||
|
||||
public static object TryParseColor32(string input)
|
||||
{
|
||||
Color32 color = default;
|
||||
|
||||
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);
|
||||
if (split.Length > 3)
|
||||
color.a = byte.Parse(split[3].Trim(), en_US);
|
||||
else
|
||||
color.a = 255;
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
public static string Color32ToString(object obj)
|
||||
{
|
||||
if (!(obj is Color32 color))
|
||||
return null;
|
||||
|
||||
// ints, this is fine
|
||||
return $"{color.r}, {color.g}, {color.b}, {color.a}";
|
||||
}
|
||||
|
||||
// Layermask (Int32)
|
||||
|
||||
public static object TryParseLayerMask(string input)
|
||||
{
|
||||
return (LayerMask)int.Parse(input);
|
||||
}
|
||||
|
||||
public static string LayerMaskToString(object obj)
|
||||
{
|
||||
if (!(obj is LayerMask mask))
|
||||
return null;
|
||||
|
||||
return mask.value.ToString();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -1,294 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
/// <summary>
|
||||
/// Syntax-highlights a member's signature, by either the Type name or a Type and Member together.
|
||||
/// </summary>
|
||||
public static class SignatureHighlighter
|
||||
{
|
||||
public const string NAMESPACE = "#a8a8a8";
|
||||
|
||||
public const string CONST = "#92c470";
|
||||
|
||||
public const string CLASS_STATIC = "#3a8d71";
|
||||
public const string CLASS_INSTANCE = "#2df7b2";
|
||||
|
||||
public const string STRUCT = "#0fba3a";
|
||||
public const string INTERFACE = "#9b9b82";
|
||||
|
||||
public const string FIELD_STATIC = "#8d8dc6";
|
||||
public const string FIELD_INSTANCE = "#c266ff";
|
||||
|
||||
public const string METHOD_STATIC = "#b55b02";
|
||||
public const string METHOD_INSTANCE = "#ff8000";
|
||||
|
||||
public const string PROP_STATIC = "#588075";
|
||||
public const string PROP_INSTANCE = "#55a38e";
|
||||
|
||||
public const string LOCAL_ARG = "#a6e9e9";
|
||||
|
||||
internal const string ARRAY_TOKEN = "[]";
|
||||
internal const string OPEN_COLOR = "<color=";
|
||||
internal const string CLOSE_COLOR = "</color>";
|
||||
internal const string OPEN_ITALIC = "<i>";
|
||||
internal const string CLOSE_ITALIC = "</i>";
|
||||
|
||||
public static readonly Color StringOrange = new Color(0.83f, 0.61f, 0.52f);
|
||||
public static readonly Color EnumGreen = new Color(0.57f, 0.76f, 0.43f);
|
||||
public static readonly Color KeywordBlue = new Color(0.3f, 0.61f, 0.83f);
|
||||
public static readonly string keywordBlueHex = KeywordBlue.ToHex();
|
||||
public static readonly Color NumberGreen = new Color(0.71f, 0.8f, 0.65f);
|
||||
|
||||
internal static string GetClassColor(Type type)
|
||||
{
|
||||
if (type.IsAbstract && type.IsSealed)
|
||||
return CLASS_STATIC;
|
||||
else if (type.IsEnum || type.IsGenericParameter)
|
||||
return CONST;
|
||||
else if (type.IsValueType)
|
||||
return STRUCT;
|
||||
else if (type.IsInterface)
|
||||
return INTERFACE;
|
||||
else
|
||||
return CLASS_INSTANCE;
|
||||
}
|
||||
|
||||
//private static readonly StringBuilder syntaxBuilder = new StringBuilder(2156);
|
||||
|
||||
private static bool GetNamespace(Type type, out string ns)
|
||||
{
|
||||
var ret = !string.IsNullOrEmpty(ns = type.Namespace?.Trim());
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string Parse(Type type, bool includeNamespace, MemberInfo memberInfo = null)
|
||||
{
|
||||
if (type == null)
|
||||
throw new ArgumentNullException("type");
|
||||
|
||||
var syntaxBuilder = new StringBuilder();
|
||||
|
||||
// Namespace
|
||||
|
||||
bool isGeneric = type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter);
|
||||
|
||||
if (!isGeneric)
|
||||
{
|
||||
if (includeNamespace && GetNamespace(type, out string ns))
|
||||
syntaxBuilder.Append(OPEN_COLOR).Append(NAMESPACE).Append('>').Append(ns).Append(CLOSE_COLOR).Append('.');
|
||||
|
||||
// Declaring type
|
||||
|
||||
var declaring = type.DeclaringType;
|
||||
while (declaring != null)
|
||||
{
|
||||
syntaxBuilder.Append(HighlightType(declaring));
|
||||
syntaxBuilder.Append('.');
|
||||
declaring = declaring.DeclaringType;
|
||||
}
|
||||
}
|
||||
|
||||
// Highlight the type name
|
||||
|
||||
syntaxBuilder.Append(HighlightType(type));
|
||||
|
||||
// If memberInfo, highlight the member info
|
||||
|
||||
if (memberInfo != null)
|
||||
{
|
||||
syntaxBuilder.Append('.');
|
||||
|
||||
int start = syntaxBuilder.Length - 1;
|
||||
syntaxBuilder.Append(OPEN_COLOR)
|
||||
.Append(GetMemberInfoColor(memberInfo, out bool isStatic))
|
||||
.Append('>')
|
||||
.Append(memberInfo.Name)
|
||||
.Append(CLOSE_COLOR);
|
||||
|
||||
if (isStatic)
|
||||
{
|
||||
syntaxBuilder.Insert(start, OPEN_ITALIC);
|
||||
syntaxBuilder.Append(CLOSE_ITALIC);
|
||||
}
|
||||
|
||||
if (memberInfo is MethodInfo method)
|
||||
{
|
||||
var args = method.GetGenericArguments();
|
||||
if (args.Length > 0)
|
||||
syntaxBuilder.Append('<').Append(ParseGenericArgs(args, true)).Append('>');
|
||||
}
|
||||
}
|
||||
|
||||
return syntaxBuilder.ToString();
|
||||
}
|
||||
|
||||
private static readonly Dictionary<string, string> typeToRichType = new Dictionary<string, string>();
|
||||
|
||||
private static bool EndsWith(this StringBuilder sb, string _string)
|
||||
{
|
||||
int len = _string.Length;
|
||||
|
||||
if (sb.Length < len)
|
||||
return false;
|
||||
|
||||
int stringpos = 0;
|
||||
for (int i = sb.Length - len; i < sb.Length; i++, stringpos++)
|
||||
{
|
||||
if (sb[i] != _string[stringpos])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static string HighlightType(Type type)
|
||||
{
|
||||
string key = type.ToString();
|
||||
|
||||
if (typeToRichType.ContainsKey(key))
|
||||
return typeToRichType[key];
|
||||
|
||||
var sb = new StringBuilder(type.Name);
|
||||
|
||||
bool isArray = false;
|
||||
if (sb.EndsWith(ARRAY_TOKEN))
|
||||
{
|
||||
isArray = true;
|
||||
sb.Remove(sb.Length - 2, 2);
|
||||
type = type.GetElementType();
|
||||
}
|
||||
|
||||
if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter))
|
||||
{
|
||||
sb.Insert(0, $"<color={CONST}>");
|
||||
sb.Append(CLOSE_COLOR);
|
||||
}
|
||||
else
|
||||
{
|
||||
var args = type.GetGenericArguments();
|
||||
|
||||
if (args.Length > 0)
|
||||
{
|
||||
// remove the `N from the end of the type name
|
||||
// this could actually be >9 in some cases, so get the length of the length string and use that.
|
||||
// eg, if it was "List`15", we would remove the ending 3 chars
|
||||
|
||||
int suffixLen = 1 + args.Length.ToString().Length;
|
||||
|
||||
// make sure the typename actually has expected "`N" format.
|
||||
if (sb[sb.Length - suffixLen] == '`')
|
||||
sb.Remove(sb.Length - suffixLen, suffixLen);
|
||||
}
|
||||
|
||||
// highlight the base name itself
|
||||
// do this after removing the `N suffix, so only the name itself is in the color tags.
|
||||
sb.Insert(0, $"{OPEN_COLOR}{GetClassColor(type)}>");
|
||||
sb.Append(CLOSE_COLOR);
|
||||
|
||||
// parse the generic args, if any
|
||||
if (args.Length > 0)
|
||||
{
|
||||
sb.Append('<').Append(ParseGenericArgs(args)).Append('>');
|
||||
}
|
||||
}
|
||||
|
||||
if (isArray)
|
||||
sb.Append('[').Append(']');
|
||||
|
||||
var ret = sb.ToString();
|
||||
typeToRichType.Add(key, ret);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static string ParseGenericArgs(Type[] args, bool isGenericParams = false)
|
||||
{
|
||||
if (args.Length < 1)
|
||||
return string.Empty;
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
sb.Append(',').Append(' ');
|
||||
|
||||
if (isGenericParams)
|
||||
{
|
||||
sb.Append(OPEN_COLOR).Append(CONST).Append('>').Append(args[i].Name).Append(CLOSE_COLOR);
|
||||
continue;
|
||||
}
|
||||
|
||||
sb.Append(HighlightType(args[i]));
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string GetMemberInfoColor(MemberTypes type)
|
||||
{
|
||||
switch (type)
|
||||
{
|
||||
case MemberTypes.Method: return METHOD_INSTANCE;
|
||||
case MemberTypes.Property: return PROP_INSTANCE;
|
||||
case MemberTypes.Field: return FIELD_INSTANCE;
|
||||
default: return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static string GetMemberInfoColor(MemberInfo memberInfo, out bool isStatic)
|
||||
{
|
||||
isStatic = false;
|
||||
if (memberInfo is FieldInfo fi)
|
||||
{
|
||||
if (fi.IsStatic)
|
||||
{
|
||||
isStatic = true;
|
||||
return FIELD_STATIC;
|
||||
}
|
||||
|
||||
return FIELD_INSTANCE;
|
||||
}
|
||||
else if (memberInfo is MethodInfo mi)
|
||||
{
|
||||
if (mi.IsStatic)
|
||||
{
|
||||
isStatic = true;
|
||||
return METHOD_STATIC;
|
||||
}
|
||||
|
||||
return METHOD_INSTANCE;
|
||||
}
|
||||
else if (memberInfo is PropertyInfo pi)
|
||||
{
|
||||
if (pi.GetAccessors(true)[0].IsStatic)
|
||||
{
|
||||
isStatic = true;
|
||||
return PROP_STATIC;
|
||||
}
|
||||
|
||||
return PROP_INSTANCE;
|
||||
}
|
||||
//else if (memberInfo is EventInfo ei)
|
||||
//{
|
||||
// if (ei.GetAddMethod().IsStatic)
|
||||
// {
|
||||
// isStatic = true;
|
||||
// return EVENT_STATIC;
|
||||
// }
|
||||
|
||||
// return EVENT_INSTANCE;
|
||||
//}
|
||||
|
||||
throw new NotImplementedException(memberInfo.GetType().Name + " is not supported");
|
||||
}
|
||||
}
|
||||
}
|
@ -1,170 +0,0 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public static class ToStringUtility
|
||||
{
|
||||
internal static Dictionary<string, MethodInfo> toStringMethods = new Dictionary<string, MethodInfo>();
|
||||
|
||||
private const string nullString = "<color=grey>null</color>";
|
||||
private const string nullUnknown = nullString + " (?)";
|
||||
private const string destroyedString = "<color=red>Destroyed</color>";
|
||||
private const string untitledString = "<i><color=grey>untitled</color></i>";
|
||||
|
||||
private const string eventSystemNamespace = "UnityEngine.EventSystem";
|
||||
|
||||
public static string PruneString(string s, int chars = 200, int lines = 5)
|
||||
{
|
||||
if (string.IsNullOrEmpty(s))
|
||||
return s;
|
||||
|
||||
var sb = new StringBuilder(Math.Max(chars, s.Length));
|
||||
int newlines = 0;
|
||||
for (int i = 0; i < s.Length; i++)
|
||||
{
|
||||
if (newlines >= lines || i >= chars)
|
||||
{
|
||||
sb.Append("...");
|
||||
break;
|
||||
}
|
||||
char c = s[i];
|
||||
if (c == '\r' || c == '\n')
|
||||
newlines++;
|
||||
sb.Append(c);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
public static string ToStringWithType(object value, Type fallbackType, bool includeNamespace = true)
|
||||
{
|
||||
if (value.IsNullOrDestroyed() && fallbackType == null)
|
||||
return nullUnknown;
|
||||
|
||||
Type type = value?.GetActualType() ?? fallbackType;
|
||||
|
||||
string richType = SignatureHighlighter.Parse(type, includeNamespace);
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
if (value.IsNullOrDestroyed())
|
||||
{
|
||||
if (value == null)
|
||||
{
|
||||
sb.Append(nullString);
|
||||
AppendRichType(sb, richType);
|
||||
return sb.ToString();
|
||||
}
|
||||
else // destroyed unity object
|
||||
{
|
||||
sb.Append(destroyedString);
|
||||
AppendRichType(sb, richType);
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
if (value is UnityEngine.Object obj)
|
||||
{
|
||||
if (string.IsNullOrEmpty(obj.name))
|
||||
sb.Append(untitledString);
|
||||
else
|
||||
{
|
||||
sb.Append('"');
|
||||
sb.Append(PruneString(obj.name, 50, 1));
|
||||
sb.Append('"');
|
||||
}
|
||||
|
||||
AppendRichType(sb, richType);
|
||||
}
|
||||
else if (type.FullName.StartsWith(eventSystemNamespace))
|
||||
{
|
||||
// UnityEngine.EventSystem classes can have some obnoxious ToString results with rich text.
|
||||
sb.Append(richType);
|
||||
}
|
||||
else
|
||||
{
|
||||
var toString = ToString(value);
|
||||
|
||||
if (type.IsGenericType
|
||||
|| toString == type.FullName
|
||||
|| toString == $"{type.FullName} {type.FullName}"
|
||||
|| toString == $"Il2Cpp{type.FullName}" || type.FullName == $"Il2Cpp{toString}")
|
||||
{
|
||||
sb.Append(richType);
|
||||
}
|
||||
else // the ToString contains some actual implementation, use that value.
|
||||
{
|
||||
sb.Append(PruneString(toString, 200, 5));
|
||||
|
||||
AppendRichType(sb, richType);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
private static void AppendRichType(StringBuilder sb, string richType)
|
||||
{
|
||||
sb.Append(' ');
|
||||
sb.Append('(');
|
||||
sb.Append(richType);
|
||||
sb.Append(')');
|
||||
}
|
||||
|
||||
private static string ToString(object value)
|
||||
{
|
||||
if (value.IsNullOrDestroyed())
|
||||
{
|
||||
if (value == null)
|
||||
return nullString;
|
||||
else // destroyed unity object
|
||||
return destroyedString;
|
||||
}
|
||||
|
||||
var type = value.GetActualType();
|
||||
|
||||
// Find and cache the ToString method for this Type, if haven't already.
|
||||
|
||||
if (!toStringMethods.ContainsKey(type.AssemblyQualifiedName))
|
||||
{
|
||||
var toStringMethod = type.GetMethod("ToString", ArgumentUtility.EmptyTypes);
|
||||
toStringMethods.Add(type.AssemblyQualifiedName, toStringMethod);
|
||||
}
|
||||
|
||||
// Invoke the ToString method on the object
|
||||
|
||||
value = value.TryCast(type);
|
||||
|
||||
string toString;
|
||||
try
|
||||
{
|
||||
toString = (string)toStringMethods[type.AssemblyQualifiedName].Invoke(value, ArgumentUtility.EmptyArgs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
toString = ex.ReflectionExToString();
|
||||
}
|
||||
|
||||
toString = ReflectionUtility.ProcessTypeInString(type, toString);
|
||||
|
||||
#if CPP
|
||||
if (value is Il2CppSystem.Type cppType)
|
||||
{
|
||||
var monoType = Il2CppReflection.GetUnhollowedType(cppType);
|
||||
if (monoType != null)
|
||||
toString = ReflectionUtility.ProcessTypeInString(monoType, toString);
|
||||
}
|
||||
#endif
|
||||
|
||||
return toString;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,124 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
using UnityEngine.UI;
|
||||
using Object = UnityEngine.Object;
|
||||
|
||||
// Project-wide namespace for accessibility
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public static class UnityHelpers
|
||||
{
|
||||
// Time helpers, can't use Time.time since timeScale will affect it.
|
||||
|
||||
// default 10ms (one frame at 100fps)
|
||||
public static bool OccuredEarlierThanDefault(this float time)
|
||||
{
|
||||
return Time.realtimeSinceStartup - 0.01f >= time;
|
||||
}
|
||||
|
||||
public static bool OccuredEarlierThan(this float time, float secondsAgo)
|
||||
{
|
||||
return Time.realtimeSinceStartup - secondsAgo >= time;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check if an object is null, and if it's a UnityEngine.Object then also check if it was destroyed.
|
||||
/// </summary>
|
||||
public static bool IsNullOrDestroyed(this object obj, bool suppressWarning = true)
|
||||
{
|
||||
var unityObj = obj as Object;
|
||||
if (obj == null)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
ExplorerCore.LogWarning("The target instance is null!");
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (obj is Object)
|
||||
{
|
||||
if (!unityObj)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
ExplorerCore.LogWarning("The target UnityEngine.Object was destroyed!");
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the full Transform heirarchy path for this provided Transform.
|
||||
/// </summary>
|
||||
public static string GetTransformPath(this Transform transform, bool includeSelf = false)
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
if (includeSelf)
|
||||
sb.Append(transform.name);
|
||||
|
||||
while (transform.parent)
|
||||
{
|
||||
transform = transform.parent;
|
||||
sb.Insert(0, '/');
|
||||
sb.Insert(0, transform.name);
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Converts Color to 6-digit RGB hex code (without # symbol). Eg, RGBA(1,0,0,1) -> FF0000
|
||||
/// </summary>
|
||||
public static string ToHex(this Color color)
|
||||
{
|
||||
byte r = (byte)Mathf.Clamp(Mathf.RoundToInt(color.r * 255f), 0, 255);
|
||||
byte g = (byte)Mathf.Clamp(Mathf.RoundToInt(color.g * 255f), 0, 255);
|
||||
byte b = (byte)Mathf.Clamp(Mathf.RoundToInt(color.b * 255f), 0, 255);
|
||||
|
||||
return $"{r:X2}{g:X2}{b:X2}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Assumes the string is a 6-digit RGB Hex color code (with optional leading #) which it will parse into a UnityEngine.Color.
|
||||
/// Eg, FF0000 -> RGBA(1,0,0,1)
|
||||
/// </summary>
|
||||
public static Color ToColor(this string _string)
|
||||
{
|
||||
_string = _string.Replace("#", "");
|
||||
|
||||
if (_string.Length != 6)
|
||||
return Color.magenta;
|
||||
|
||||
var r = byte.Parse(_string.Substring(0, 2), NumberStyles.HexNumber);
|
||||
var g = byte.Parse(_string.Substring(2, 2), NumberStyles.HexNumber);
|
||||
var b = byte.Parse(_string.Substring(4, 2), NumberStyles.HexNumber);
|
||||
|
||||
var color = new Color
|
||||
{
|
||||
r = (float)(r / (decimal)255),
|
||||
g = (float)(g / (decimal)255),
|
||||
b = (float)(b / (decimal)255),
|
||||
a = 1
|
||||
};
|
||||
|
||||
return color;
|
||||
}
|
||||
|
||||
private static PropertyInfo onEndEdit;
|
||||
|
||||
public static UnityEvent<string> GetOnEndEdit(this InputField _this)
|
||||
{
|
||||
if (onEndEdit == null)
|
||||
onEndEdit = typeof(InputField).GetProperty("onEndEdit")
|
||||
?? throw new Exception("Could not get InputField.onEndEdit property!");
|
||||
|
||||
return onEndEdit.GetValue(_this, null).TryCast<UnityEvent<string>>();
|
||||
}
|
||||
}
|
||||
}
|
40
src/ExplorerBehaviour.cs
Normal file
40
src/ExplorerBehaviour.cs
Normal file
@ -0,0 +1,40 @@
|
||||
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<ExplorerBehaviour>();
|
||||
#endif
|
||||
|
||||
var obj = new GameObject("ExplorerBehaviour");
|
||||
GameObject.DontDestroyOnLoad(obj);
|
||||
obj.hideFlags |= HideFlags.HideAndDontSave;
|
||||
Instance = obj.AddComponent<ExplorerBehaviour>();
|
||||
}
|
||||
|
||||
#if CPP
|
||||
public ExplorerBehaviour(IntPtr ptr) : base(ptr) { }
|
||||
#endif
|
||||
|
||||
internal void Update()
|
||||
{
|
||||
ExplorerCore.Update();
|
||||
}
|
||||
}
|
||||
}
|
@ -1,31 +1,29 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.Tests;
|
||||
using UnityExplorer.Config;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.UI.ObjectExplorer;
|
||||
using UnityExplorer.ObjectExplorer;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.Runtime;
|
||||
using UniverseLib.Input;
|
||||
using UniverseLib.UI;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public static class ExplorerCore
|
||||
{
|
||||
public const string NAME = "UnityExplorer";
|
||||
public const string VERSION = "4.1.7";
|
||||
public const string VERSION = "4.5.2";
|
||||
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.
|
||||
@ -41,46 +39,37 @@ namespace UnityExplorer
|
||||
|
||||
Log($"{NAME} {VERSION} initializing...");
|
||||
|
||||
ExplorerBehaviour.Setup();
|
||||
|
||||
if (!Directory.Exists(Loader.ExplorerFolder))
|
||||
Directory.CreateDirectory(Loader.ExplorerFolder);
|
||||
|
||||
ConfigManager.Init(Loader.ConfigHandler);
|
||||
UERuntimeHelper.Init();
|
||||
ExplorerBehaviour.Setup();
|
||||
|
||||
ReflectionUtility.Init();
|
||||
|
||||
RuntimeProvider.Init();
|
||||
SceneHandler.Init();
|
||||
InputManager.Init();
|
||||
|
||||
RuntimeProvider.Instance.StartCoroutine(SetupCoroutine());
|
||||
|
||||
Log($"Finished core setup, waiting for UI setup...");
|
||||
UniverseLib.Universe.Init(ConfigManager.Startup_Delay_Time.Value, LateInit, Log, new()
|
||||
{
|
||||
Disable_EventSystem_Override = ConfigManager.Disable_EventSystem_Override.Value,
|
||||
Force_Unlock_Mouse = ConfigManager.Force_Unlock_Mouse.Value,
|
||||
Unhollowed_Modules_Folder = loader.UnhollowedModulesFolder
|
||||
});
|
||||
}
|
||||
|
||||
// Do a delayed setup so that objects aren't destroyed instantly.
|
||||
// This can happen for a multitude of reasons.
|
||||
// Default delay is 1 second which is usually enough.
|
||||
private static IEnumerator SetupCoroutine()
|
||||
private static void LateInit()
|
||||
{
|
||||
yield return null;
|
||||
Log($"Setting up late core features...");
|
||||
|
||||
float start = Time.realtimeSinceStartup;
|
||||
float delay = ConfigManager.Startup_Delay_Time.Value;
|
||||
|
||||
while (delay > 0)
|
||||
{
|
||||
float diff = Math.Max(Time.deltaTime, Time.realtimeSinceStartup - start);
|
||||
delay -= diff;
|
||||
yield return null;
|
||||
}
|
||||
SceneHandler.Init();
|
||||
|
||||
Log($"Creating UI...");
|
||||
|
||||
UIManager.InitUI();
|
||||
|
||||
Log($"{NAME} {VERSION} initialized.");
|
||||
|
||||
//InspectorManager.Inspect(typeof(Tests.TestClass));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -88,19 +77,11 @@ namespace UnityExplorer
|
||||
/// </summary>
|
||||
public static void Update()
|
||||
{
|
||||
RuntimeProvider.Instance.Update();
|
||||
|
||||
UIManager.Update();
|
||||
}
|
||||
|
||||
public static void FixedUpdate()
|
||||
{
|
||||
RuntimeProvider.Instance.ProcessFixedUpdate();
|
||||
}
|
||||
|
||||
public static void OnPostRender()
|
||||
{
|
||||
RuntimeProvider.Instance.ProcessOnPostRender();
|
||||
// check master toggle
|
||||
if (InputManager.GetKeyDown(ConfigManager.Master_Toggle.Value))
|
||||
UIManager.ShowMenu = !UIManager.ShowMenu;
|
||||
}
|
||||
|
||||
#region LOGGING
|
||||
|
67
src/Hooks/AddHookCell.cs
Normal file
67
src/Hooks/AddHookCell.cs
Normal file
@ -0,0 +1,67 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
|
||||
namespace UnityExplorer.Hooks
|
||||
{
|
||||
public class AddHookCell : ICell
|
||||
{
|
||||
public bool Enabled => UIRoot.activeSelf;
|
||||
|
||||
public RectTransform Rect { get; set; }
|
||||
public GameObject UIRoot { get; set; }
|
||||
|
||||
public float DefaultHeight => 30;
|
||||
|
||||
public Text MethodNameLabel;
|
||||
public Text HookedLabel;
|
||||
public ButtonRef HookButton;
|
||||
|
||||
public int CurrentDisplayedIndex;
|
||||
|
||||
private void OnHookClicked()
|
||||
{
|
||||
HookManager.Instance.AddHookClicked(CurrentDisplayedIndex);
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
this.UIRoot.SetActive(true);
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
this.UIRoot.SetActive(false);
|
||||
}
|
||||
|
||||
public GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30));
|
||||
Rect = UIRoot.GetComponent<RectTransform>();
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 5, childAlignment: TextAnchor.UpperLeft);
|
||||
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
|
||||
UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
HookedLabel = UIFactory.CreateLabel(UIRoot, "HookedLabel", "✓", TextAnchor.MiddleCenter, Color.green);
|
||||
UIFactory.SetLayoutElement(HookedLabel.gameObject, minHeight: 25, minWidth: 100);
|
||||
|
||||
HookButton = UIFactory.CreateButton(UIRoot, "HookButton", "Hook", new Color(0.2f, 0.25f, 0.2f));
|
||||
UIFactory.SetLayoutElement(HookButton.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
HookButton.OnClick += OnHookClicked;
|
||||
|
||||
MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
}
|
||||
}
|
81
src/Hooks/HookCell.cs
Normal file
81
src/Hooks/HookCell.cs
Normal file
@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
|
||||
namespace UnityExplorer.Hooks
|
||||
{
|
||||
public class HookCell : ICell
|
||||
{
|
||||
public bool Enabled => UIRoot.activeSelf;
|
||||
|
||||
public RectTransform Rect { get; set; }
|
||||
public GameObject UIRoot { get; set; }
|
||||
|
||||
public float DefaultHeight => 30;
|
||||
|
||||
public Text MethodNameLabel;
|
||||
public ButtonRef EditPatchButton;
|
||||
public ButtonRef ToggleActiveButton;
|
||||
public ButtonRef DeleteButton;
|
||||
|
||||
public int CurrentDisplayedIndex;
|
||||
|
||||
private void OnToggleActiveClicked()
|
||||
{
|
||||
HookManager.Instance.EnableOrDisableHookClicked(CurrentDisplayedIndex);
|
||||
}
|
||||
|
||||
private void OnDeleteClicked()
|
||||
{
|
||||
HookManager.Instance.DeleteHookClicked(CurrentDisplayedIndex);
|
||||
}
|
||||
|
||||
private void OnEditPatchClicked()
|
||||
{
|
||||
HookManager.Instance.EditPatchClicked(CurrentDisplayedIndex);
|
||||
}
|
||||
|
||||
public GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30));
|
||||
Rect = UIRoot.GetComponent<RectTransform>();
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft);
|
||||
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
|
||||
UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
|
||||
MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
ToggleActiveButton = UIFactory.CreateButton(UIRoot, "ToggleActiveBtn", "Enabled", new Color(0.15f, 0.2f, 0.15f));
|
||||
UIFactory.SetLayoutElement(ToggleActiveButton.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
ToggleActiveButton.OnClick += OnToggleActiveClicked;
|
||||
|
||||
DeleteButton = UIFactory.CreateButton(UIRoot, "DeleteButton", "Delete", new Color(0.2f, 0.15f, 0.15f));
|
||||
UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
DeleteButton.OnClick += OnDeleteClicked;
|
||||
|
||||
EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Edit Hook Source", new Color(0.15f, 0.15f, 0.15f));
|
||||
UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 150);
|
||||
EditPatchButton.OnClick += OnEditPatchClicked;
|
||||
|
||||
return UIRoot;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
UIRoot.SetActive(false);
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
UIRoot.SetActive(true);
|
||||
}
|
||||
}
|
||||
}
|
236
src/Hooks/HookInstance.cs
Normal file
236
src/Hooks/HookInstance.cs
Normal file
@ -0,0 +1,236 @@
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using HarmonyLib;
|
||||
using Mono.CSharp;
|
||||
using UnityExplorer.CSConsole;
|
||||
using UniverseLib;
|
||||
|
||||
namespace UnityExplorer.Hooks
|
||||
{
|
||||
public class HookInstance
|
||||
{
|
||||
// Static
|
||||
|
||||
private static readonly StringBuilder evalOutput = new StringBuilder();
|
||||
private static readonly ScriptEvaluator scriptEvaluator = new ScriptEvaluator(new StringWriter(evalOutput));
|
||||
|
||||
static HookInstance()
|
||||
{
|
||||
scriptEvaluator.Run("using System;");
|
||||
scriptEvaluator.Run("using System.Reflection;");
|
||||
scriptEvaluator.Run("using System.Collections;");
|
||||
scriptEvaluator.Run("using System.Collections.Generic;");
|
||||
}
|
||||
|
||||
// Instance
|
||||
|
||||
public bool Enabled;
|
||||
public MethodInfo TargetMethod;
|
||||
public string PatchSourceCode;
|
||||
|
||||
private readonly string shortSignature;
|
||||
private PatchProcessor patchProcessor;
|
||||
|
||||
private MethodInfo postfix;
|
||||
private MethodInfo prefix;
|
||||
private MethodInfo finalizer;
|
||||
private MethodInfo transpiler;
|
||||
|
||||
public HookInstance(MethodInfo targetMethod)
|
||||
{
|
||||
this.TargetMethod = targetMethod;
|
||||
this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
|
||||
|
||||
GenerateDefaultPatchSourceCode(targetMethod);
|
||||
|
||||
if (CompileAndGenerateProcessor(PatchSourceCode))
|
||||
Patch();
|
||||
}
|
||||
|
||||
// Evaluator.source_file
|
||||
private static readonly FieldInfo fi_sourceFile = AccessTools.Field(typeof(Evaluator), "source_file");
|
||||
// TypeDefinition.Definition
|
||||
private static readonly PropertyInfo pi_Definition = AccessTools.Property(typeof(TypeDefinition), "Definition");
|
||||
|
||||
public bool CompileAndGenerateProcessor(string patchSource)
|
||||
{
|
||||
Unpatch();
|
||||
|
||||
try
|
||||
{
|
||||
patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod);
|
||||
|
||||
// Dynamically compile the patch method
|
||||
|
||||
var codeBuilder = new StringBuilder();
|
||||
|
||||
codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}");
|
||||
codeBuilder.AppendLine("{");
|
||||
codeBuilder.AppendLine(patchSource);
|
||||
codeBuilder.AppendLine("}");
|
||||
|
||||
scriptEvaluator.Run(codeBuilder.ToString());
|
||||
|
||||
if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
|
||||
throw new FormatException($"Unable to compile the generated patch!");
|
||||
|
||||
// TODO: Publicize MCS to avoid this reflection
|
||||
// Get the most recent Patch type in the source file
|
||||
var typeContainer = ((CompilationSourceFile)fi_sourceFile.GetValue(scriptEvaluator))
|
||||
.Containers
|
||||
.Last(it => it.MemberName.Name.StartsWith("DynamicPatch_"));
|
||||
// Get the TypeSpec from the TypeDefinition, then get its "MetaInfo" (System.Type)
|
||||
var patchClass = ((TypeSpec)pi_Definition.GetValue((Class)typeContainer, null)).GetMetaInfo();
|
||||
|
||||
// Create the harmony patches as defined
|
||||
|
||||
postfix = patchClass.GetMethod("Postfix", ReflectionUtility.FLAGS);
|
||||
if (postfix != null)
|
||||
patchProcessor.AddPostfix(new HarmonyMethod(postfix));
|
||||
|
||||
prefix = patchClass.GetMethod("Prefix", ReflectionUtility.FLAGS);
|
||||
if (prefix != null)
|
||||
patchProcessor.AddPrefix(new HarmonyMethod(prefix));
|
||||
|
||||
finalizer = patchClass.GetMethod("Finalizer", ReflectionUtility.FLAGS);
|
||||
if (finalizer != null)
|
||||
patchProcessor.AddFinalizer(new HarmonyMethod(finalizer));
|
||||
|
||||
transpiler = patchClass.GetMethod("Transpiler", ReflectionUtility.FLAGS);
|
||||
if (transpiler != null)
|
||||
patchProcessor.AddTranspiler(new HarmonyMethod(transpiler));
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception creating patch processor for target method {TargetMethod.FullDescription()}!\r\n{ex}");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private string GenerateDefaultPatchSourceCode(MethodInfo targetMethod)
|
||||
{
|
||||
var codeBuilder = new StringBuilder();
|
||||
// Arguments
|
||||
|
||||
codeBuilder.Append("public static void Postfix(System.Reflection.MethodBase __originalMethod");
|
||||
|
||||
if (!targetMethod.IsStatic)
|
||||
codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance");
|
||||
|
||||
if (targetMethod.ReturnType != typeof(void))
|
||||
codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result");
|
||||
|
||||
var parameters = targetMethod.GetParameters();
|
||||
|
||||
int paramIdx = 0;
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
codeBuilder.Append($", {param.ParameterType.FullDescription().Replace("&", "")} __{paramIdx}");
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
codeBuilder.Append(")\n");
|
||||
|
||||
// Patch body
|
||||
|
||||
codeBuilder.AppendLine("{");
|
||||
|
||||
codeBuilder.AppendLine(" try {");
|
||||
|
||||
// Log message
|
||||
|
||||
var logMessage = new StringBuilder();
|
||||
logMessage.Append($"Patch called: {shortSignature}\\n");
|
||||
|
||||
if (!targetMethod.IsStatic)
|
||||
logMessage.Append("__instance: {__instance.ToString()}\\n");
|
||||
|
||||
paramIdx = 0;
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
logMessage.Append($"Parameter {paramIdx} {param.Name}: ");
|
||||
Type pType = param.ParameterType;
|
||||
if (pType.IsByRef) pType = pType.GetElementType();
|
||||
if (pType.IsValueType)
|
||||
logMessage.Append($"{{__{paramIdx}.ToString()}}");
|
||||
else
|
||||
logMessage.Append($"{{__{paramIdx}?.ToString() ?? \"null\"}}");
|
||||
logMessage.Append("\\n");
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
if (targetMethod.ReturnType != typeof(void))
|
||||
{
|
||||
logMessage.Append("Return value: ");
|
||||
if (targetMethod.ReturnType.IsValueType)
|
||||
logMessage.Append("{__result.ToString()}");
|
||||
else
|
||||
logMessage.Append("{__result?.ToString() ?? \"null\"}");
|
||||
logMessage.Append("\\n");
|
||||
}
|
||||
|
||||
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log($\"{logMessage}\");");
|
||||
codeBuilder.AppendLine(" }");
|
||||
codeBuilder.AppendLine(" catch (System.Exception ex) {");
|
||||
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");");
|
||||
codeBuilder.AppendLine(" }");
|
||||
|
||||
// End patch body
|
||||
|
||||
codeBuilder.AppendLine("}");
|
||||
|
||||
//ExplorerCore.Log(codeBuilder.ToString());
|
||||
|
||||
return PatchSourceCode = codeBuilder.ToString();
|
||||
}
|
||||
|
||||
public void TogglePatch()
|
||||
{
|
||||
if (!Enabled)
|
||||
Patch();
|
||||
else
|
||||
Unpatch();
|
||||
}
|
||||
|
||||
public void Patch()
|
||||
{
|
||||
try
|
||||
{
|
||||
patchProcessor.Patch();
|
||||
|
||||
Enabled = true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception hooking method!\r\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
public void Unpatch()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (prefix != null)
|
||||
patchProcessor.Unpatch(prefix);
|
||||
if (postfix != null)
|
||||
patchProcessor.Unpatch(postfix);
|
||||
if (finalizer != null)
|
||||
patchProcessor.Unpatch(finalizer);
|
||||
if (transpiler != null)
|
||||
patchProcessor.Unpatch(transpiler);
|
||||
|
||||
Enabled = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception unpatching method: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
231
src/Hooks/HookManager.cs
Normal file
231
src/Hooks/HookManager.cs
Normal file
@ -0,0 +1,231 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Specialized;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using HarmonyLib;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Runtime;
|
||||
using UnityExplorer.CSConsole;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI.Widgets;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.Hooks
|
||||
{
|
||||
public class HookManager : ICellPoolDataSource<HookCell>, ICellPoolDataSource<AddHookCell>
|
||||
{
|
||||
private static HookManager s_instance;
|
||||
public static HookManager Instance => s_instance ?? (s_instance = new HookManager());
|
||||
|
||||
public HookManagerPanel Panel => UIManager.GetPanel<HookManagerPanel>(UIManager.Panels.HookManager);
|
||||
|
||||
// This class acts as the data source for both current hooks and eligable methods when adding hooks.
|
||||
// 'isAddingMethods' keeps track of which pool is currently the displayed one, so our ItemCount reflects the
|
||||
// correct pool cells.
|
||||
private bool isAddingMethods;
|
||||
public int ItemCount => isAddingMethods ? filteredEligableMethods.Count : currentHooks.Count;
|
||||
|
||||
// current hooks
|
||||
private readonly HashSet<string> hookedSignatures = new HashSet<string>();
|
||||
private readonly OrderedDictionary currentHooks = new OrderedDictionary();
|
||||
|
||||
// adding hooks
|
||||
private readonly List<MethodInfo> currentAddEligableMethods = new List<MethodInfo>();
|
||||
private readonly List<MethodInfo> filteredEligableMethods = new List<MethodInfo>();
|
||||
|
||||
// hook editor
|
||||
private readonly LexerBuilder Lexer = new LexerBuilder();
|
||||
private HookInstance currentEditedHook;
|
||||
|
||||
// ~~~~~~~~~~~ Main Current Hooks window ~~~~~~~~~~~
|
||||
|
||||
public void EnableOrDisableHookClicked(int index)
|
||||
{
|
||||
var hook = (HookInstance)currentHooks[index];
|
||||
hook.TogglePatch();
|
||||
|
||||
Panel.HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void DeleteHookClicked(int index)
|
||||
{
|
||||
var hook = (HookInstance)currentHooks[index];
|
||||
hook.Unpatch();
|
||||
currentHooks.RemoveAt(index);
|
||||
hookedSignatures.Remove(hook.TargetMethod.FullDescription());
|
||||
|
||||
Panel.HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void EditPatchClicked(int index)
|
||||
{
|
||||
Panel.SetPage(HookManagerPanel.Pages.HookSourceEditor);
|
||||
var hook = (HookInstance)currentHooks[index];
|
||||
currentEditedHook = hook;
|
||||
Panel.EditorInput.Text = hook.PatchSourceCode;
|
||||
}
|
||||
|
||||
// Set current hook cell
|
||||
|
||||
public void OnCellBorrowed(HookCell cell) { }
|
||||
|
||||
public void SetCell(HookCell cell, int index)
|
||||
{
|
||||
if (index >= this.currentHooks.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
cell.CurrentDisplayedIndex = index;
|
||||
var hook = (HookInstance)this.currentHooks[index];
|
||||
|
||||
cell.MethodNameLabel.text = SignatureHighlighter.HighlightMethod(hook.TargetMethod);
|
||||
|
||||
cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "Enabled" : "Disabled";
|
||||
RuntimeHelper.SetColorBlockAuto(cell.ToggleActiveButton.Component,
|
||||
hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f));
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~ Add Hooks window ~~~~~~~~~~~
|
||||
|
||||
public void OnClassSelectedForHooks(string typeFullName)
|
||||
{
|
||||
var type = ReflectionUtility.GetTypeByName(typeFullName);
|
||||
if (type == null)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Could not find any type by name {typeFullName}!");
|
||||
return;
|
||||
}
|
||||
|
||||
Panel.SetAddHooksLabelType(SignatureHighlighter.Parse(type, true));
|
||||
|
||||
Panel.ResetMethodFilter();
|
||||
filteredEligableMethods.Clear();
|
||||
currentAddEligableMethods.Clear();
|
||||
foreach (var method in type.GetMethods(ReflectionUtility.FLAGS))
|
||||
{
|
||||
if (method.IsGenericMethod || UERuntimeHelper.IsBlacklisted(method))
|
||||
continue;
|
||||
currentAddEligableMethods.Add(method);
|
||||
filteredEligableMethods.Add(method);
|
||||
}
|
||||
|
||||
isAddingMethods = true;
|
||||
Panel.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
|
||||
Panel.AddHooksScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
public void DoneAddingHooks()
|
||||
{
|
||||
isAddingMethods = false;
|
||||
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
|
||||
Panel.HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void AddHookClicked(int index)
|
||||
{
|
||||
if (index >= this.filteredEligableMethods.Count)
|
||||
return;
|
||||
|
||||
AddHook(filteredEligableMethods[index]);
|
||||
Panel.AddHooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void AddHook(MethodInfo method)
|
||||
{
|
||||
var sig = method.FullDescription();
|
||||
if (hookedSignatures.Contains(sig))
|
||||
return;
|
||||
|
||||
var hook = new HookInstance(method);
|
||||
if (hook.Enabled)
|
||||
{
|
||||
hookedSignatures.Add(sig);
|
||||
currentHooks.Add(sig, hook);
|
||||
}
|
||||
}
|
||||
|
||||
public void OnAddHookFilterInputChanged(string input)
|
||||
{
|
||||
filteredEligableMethods.Clear();
|
||||
|
||||
if (string.IsNullOrEmpty(input))
|
||||
filteredEligableMethods.AddRange(currentAddEligableMethods);
|
||||
else
|
||||
{
|
||||
foreach (var method in currentAddEligableMethods)
|
||||
{
|
||||
if (method.Name.ContainsIgnoreCase(input))
|
||||
filteredEligableMethods.Add(method);
|
||||
}
|
||||
}
|
||||
|
||||
Panel.AddHooksScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
// Set eligable method cell
|
||||
|
||||
public void OnCellBorrowed(AddHookCell cell) { }
|
||||
|
||||
public void SetCell(AddHookCell cell, int index)
|
||||
{
|
||||
if (index >= this.filteredEligableMethods.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
cell.CurrentDisplayedIndex = index;
|
||||
var method = this.filteredEligableMethods[index];
|
||||
|
||||
cell.MethodNameLabel.text = SignatureHighlighter.HighlightMethod(method);
|
||||
|
||||
var sig = method.FullDescription();
|
||||
if (hookedSignatures.Contains(sig))
|
||||
{
|
||||
cell.HookButton.Component.gameObject.SetActive(false);
|
||||
cell.HookedLabel.gameObject.SetActive(true);
|
||||
}
|
||||
else
|
||||
{
|
||||
cell.HookButton.Component.gameObject.SetActive(true);
|
||||
cell.HookedLabel.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~ Hook source editor window ~~~~~~~~~~~
|
||||
|
||||
public void OnEditorInputChanged(string value)
|
||||
{
|
||||
Panel.EditorHighlightText.text = Lexer.BuildHighlightedString(value, 0, value.Length - 1, 0,
|
||||
Panel.EditorInput.Component.caretPosition, out _);
|
||||
}
|
||||
|
||||
public void EditorInputCancel()
|
||||
{
|
||||
currentEditedHook = null;
|
||||
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
|
||||
}
|
||||
|
||||
public void EditorInputSave()
|
||||
{
|
||||
var input = Panel.EditorInput.Text;
|
||||
bool wasEnabled = currentEditedHook.Enabled;
|
||||
if (currentEditedHook.CompileAndGenerateProcessor(input))
|
||||
{
|
||||
if (wasEnabled)
|
||||
currentEditedHook.Patch();
|
||||
currentEditedHook.PatchSourceCode = input;
|
||||
currentEditedHook = null;
|
||||
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -6,26 +6,17 @@
|
||||
<ItemGroup>
|
||||
<InputAssemblies Include="$(OutputPath)$(AssemblyName).dll" />
|
||||
<InputAssemblies Include="..\lib\mcs-unity\mcs\bin\Release\mcs.dll" />
|
||||
<InputAssemblies Include="packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll" />
|
||||
<InputAssemblies Include="packages\Samboy063.Tomlet.3.1.3\lib\net35\Tomlet.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\" />
|
||||
<ReferenceFolders Include="..\lib\MelonLoader\" />
|
||||
<ReferenceFolders Include="..\lib\Il2CppAssemblyUnhollower\UnhollowerBaseLib\bin\Release\net4.7.2\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ILRepack
|
||||
|
@ -5,13 +5,19 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UniverseLib.Input;
|
||||
using UnityExplorer.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
using UniverseLib.UI.Widgets;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI.Widgets.ScrollView;
|
||||
using UniverseLib.Utility;
|
||||
|
||||
namespace UnityExplorer.UI.Inspectors
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class GameObjectInspector : InspectorBase
|
||||
{
|
||||
@ -40,7 +46,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
GOControls.UpdateGameObjectInfo(true, true);
|
||||
GOControls.UpdateTransformControlValues(true);
|
||||
|
||||
RuntimeProvider.Instance.StartCoroutine(InitCoroutine());
|
||||
RuntimeHelper.StartCoroutine(InitCoroutine());
|
||||
}
|
||||
|
||||
private IEnumerator InitCoroutine()
|
||||
@ -223,7 +229,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
{
|
||||
try
|
||||
{
|
||||
RuntimeProvider.Instance.AddComponent<Component>(GOTarget, type);
|
||||
RuntimeHelper.AddComponent<Component>(GOTarget, type);
|
||||
UpdateComponents();
|
||||
}
|
||||
catch (Exception ex)
|
||||
@ -237,7 +243,6 @@ namespace UnityExplorer.UI.Inspectors
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#region UI Construction
|
||||
|
||||
public override GameObject CreateContent(GameObject parent)
|
@ -4,9 +4,12 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UniverseLib;
|
||||
using UniverseLib.UI;
|
||||
using UniverseLib.UI.Models;
|
||||
using UniverseLib.UI.Widgets.ButtonList;
|
||||
|
||||
namespace UnityExplorer.UI.Inspectors
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class ComponentCell : ButtonCell
|
||||
{
|
||||
@ -31,7 +34,8 @@ namespace UnityExplorer.UI.Inspectors
|
||||
var root = base.CreateContent(parent);
|
||||
|
||||
// Add mask to button so text doesnt overlap on Close button
|
||||
this.Button.Component.gameObject.AddComponent<Mask>().showMaskGraphic = true;
|
||||
//this.Button.Component.gameObject.AddComponent<Mask>().showMaskGraphic = true;
|
||||
this.Button.ButtonText.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
|
||||
// Behaviour toggle
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user