mirror of
https://github.com/sinai-dev/UnityExplorer.git
synced 2025-06-23 00:52:31 +08:00
Compare commits
196 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
92447b55cd | |||
87d5d5a2de | |||
08cff3386b | |||
6033200579 | |||
94ec1c4908 | |||
7a400e762c | |||
67f9f744bb | |||
7a59f9a2a1 | |||
9b42eef1b9 | |||
830000b019 | |||
34910ab273 | |||
86b036095e | |||
b57e5be2e6 | |||
2d8ae45814 | |||
66dc262a68 | |||
4342901206 | |||
58b7c72a5c | |||
623dc7b7be | |||
e6f4939cc9 | |||
7a539ba78b | |||
0d10f94eb5 | |||
91671bf243 | |||
a72877befb | |||
16335c1bc4 | |||
8fab9e6268 | |||
fdd9039cca | |||
dcca980635 | |||
bfcab8248e | |||
97c20144f1 | |||
aaab10a0a0 | |||
b42a8dbe6a | |||
d7008db22e | |||
8c1913fe80 | |||
8d8c9ac7c9 | |||
f35beeaf58 | |||
d150ff3455 | |||
c4fa0d6bcd | |||
a5f56cf5a3 | |||
8c822b2ee9 | |||
4681b7e192 | |||
97093733d8 | |||
615f77979f | |||
ba3cf970d9 | |||
b2fb571b18 | |||
67ce6f946a | |||
1b82ccb49f | |||
480eb5afd5 | |||
88ea2a09c9 | |||
a678aa4d78 | |||
eaf478e314 | |||
d391968b32 | |||
70349ad7c7 | |||
78f2d1070f | |||
51307563ab | |||
185d1aaa0f | |||
3d6e8fcbf8 | |||
1daf4fade4 | |||
d92fb3f83f | |||
fe24b68fe2 | |||
6bf92b9a96 | |||
9f1cab019d | |||
a131404ac7 | |||
773900d749 | |||
342fc6bdb8 | |||
e85ea6ac3a | |||
af889e64cb | |||
0274022ce4 | |||
211576e0f8 | |||
3e42d7479f | |||
df4dea20c1 | |||
ece0c43067 | |||
6311c8d09a | |||
04739d0be8 | |||
a46acba265 | |||
5515b2eae4 | |||
9992029e28 | |||
14105785f0 | |||
365269b0dd | |||
0b973393d1 | |||
701d4431ae | |||
bfa73bcb55 | |||
b0bbeb3cf8 | |||
1a26623080 | |||
041f2938f7 | |||
9e7bb1a625 | |||
36f23b7cdc | |||
b51b743df4 | |||
805aff07cc | |||
bcdaf3b97e | |||
cb8e947fdf | |||
c8899be3ae | |||
cd5c69c965 | |||
5427312f18 | |||
eb7e80d910 | |||
a54888ae3a | |||
4f0553d293 | |||
9f0f7f9b57 | |||
383c6f19e8 | |||
428fab28f9 | |||
760b2981ad | |||
eee7d6bcc4 | |||
e270f205a1 | |||
084aee617c | |||
d0e508727a | |||
a9a53ba924 | |||
3cd9819790 | |||
5abfa3da67 | |||
e5d2d29a47 | |||
6a47e542e5 | |||
f1b83e7c9e | |||
44c6503ae2 | |||
6970dcbbc7 | |||
ac9c2d5286 | |||
496a5de55e | |||
b062924af7 | |||
5aef8ddc99 | |||
c134c1752e |
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
|
97
.github/workflows/dotnet.yml
vendored
Normal file
97
.github/workflows/dotnet.yml
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
name: Build UnityExplorer
|
||||
|
||||
# Controls when the action will run.
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: windows-latest
|
||||
if: "!contains(github.event.head_commit.message, '-noci')"
|
||||
|
||||
steps:
|
||||
# Checkout latest with submodules
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
submodules: recursive
|
||||
|
||||
# Setup tools
|
||||
- name: Setup msbuild
|
||||
uses: microsoft/setup-msbuild@v1
|
||||
|
||||
- name: Setup nuget
|
||||
uses: nuget/setup-nuget@v1
|
||||
with:
|
||||
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: nuget restore src\UnityExplorer.sln
|
||||
|
||||
# BepInEx Il2Cpp
|
||||
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_BIE_Cpp
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: UnityExplorer.BepInEx.Il2Cpp
|
||||
path: ./Release/UnityExplorer.BepInEx.Il2Cpp/*
|
||||
|
||||
# BepInEx 5 Mono
|
||||
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_BIE5_Mono
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: UnityExplorer.BepInEx5.Mono
|
||||
path: ./Release/UnityExplorer.BepInEx5.Mono/*
|
||||
|
||||
# BepInEx 6 Mono
|
||||
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_BIE6_Mono
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: UnityExplorer.BepInEx6.Mono
|
||||
path: ./Release/UnityExplorer.BepInEx6.Mono/*
|
||||
|
||||
# MelonLoader Il2Cpp
|
||||
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_ML_Cpp
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: UnityExplorer.MelonLoader.Il2Cpp
|
||||
path: ./Release/UnityExplorer.MelonLoader.Il2Cpp/*
|
||||
|
||||
# MelonLoader Mono
|
||||
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_ML_Mono
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: UnityExplorer.MelonLoader.Mono
|
||||
path: ./Release/UnityExplorer.MelonLoader.Mono/*
|
||||
|
||||
# Standalone Il2Cpp
|
||||
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_STANDALONE_Cpp
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: UnityExplorer.Standalone.Il2Cpp
|
||||
path: ./Release/UnityExplorer.Standalone.Il2Cpp/*
|
||||
|
||||
# Standalone Mono
|
||||
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_STANDALONE_Mono
|
||||
|
||||
- uses: actions/upload-artifact@v2
|
||||
with:
|
||||
name: UnityExplorer.Standalone.Mono
|
||||
path: ./Release/UnityExplorer.Standalone.Mono/*
|
||||
|
3
.gitmodules
vendored
3
.gitmodules
vendored
@ -1,9 +1,6 @@
|
||||
[submodule "lib/Il2CppAssemblyUnhollower"]
|
||||
path = lib/Il2CppAssemblyUnhollower
|
||||
url = https://github.com/knah/Il2CppAssemblyUnhollower
|
||||
[submodule "lib/HarmonyX"]
|
||||
path = lib/HarmonyX
|
||||
url = https://github.com/BepInEx/HarmonyX
|
||||
[submodule "lib/mcs-unity"]
|
||||
path = lib/mcs-unity
|
||||
url = https://github.com/sinai-dev/mcs-unity
|
||||
|
118
README.md
118
README.md
@ -3,45 +3,43 @@
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
An in-game explorer and a suite of debugging tools for <a href="https://docs.unity3d.com/Manual/IL2CPP.html">IL2CPP</a> and <b>Mono</b> Unity games, to aid with modding development.
|
||||
🔍 An in-game UI for exploring, debugging and modifying Unity games.
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
Supports most Unity games from versions 5.2 to 2020+.
|
||||
✔️ Supports most Unity versions from 5.2 to 2021+ (IL2CPP and Mono).
|
||||
</p>
|
||||
|
||||
## Releases [](../../releases/latest) [](../../releases) [](../../releases/latest)
|
||||
# Releases [](../../releases)
|
||||
|
||||
| Mod Loader | IL2CPP | Mono |
|
||||
| ----------- | ------ | ---- |
|
||||
| [BepInEx](https://github.com/BepInEx/BepInEx) 6.X | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx6.Mono.zip) |
|
||||
| [BepInEx](https://github.com/BepInEx/BepInEx) 5.X | ✖️ n/a | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx5.Mono.zip) |
|
||||
| [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) 0.3.1 | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Mono.zip) |
|
||||
| Standalone | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.Standalone.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.Standalone.Mono.zip) |
|
||||
[](../../releases/latest) [](https://github.com/sinai-dev/UnityExplorer/actions) [](../../releases/latest)
|
||||
|
||||
### Known issues
|
||||
* UI layouts broken/unusable after changing resolutions: delete the file `data.ini` in the UnityExplorer folder (same place as where you put the DLL). Better fix being worked on.
|
||||
* Any `MissingMethodException` or `NotSupportedException`: please report the issue and provide a copy of your mod loader log and/or Unity log.
|
||||
* The C# console may unexpectedly produce a GC Mark Overflow crash when calling certain outside methods. Not clear yet what is causing this, but it's being looked into.
|
||||
* In IL2CPP, some IEnumerable and IDictionary types may fail enumeration. Waiting for the Unhollower rewrite to address this any further.
|
||||
* In IL2CPP, the C# console might not suggest deobfuscated (or obfuscated) names. Being looked into.
|
||||
⚡ 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)
|
||||
|
||||
## How to install
|
||||
## BepInEx
|
||||
|
||||
### BepInEx
|
||||
| Release | IL2CPP | Mono |
|
||||
| ------- | ------ | ---- |
|
||||
| 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. Install [BepInEx](https://github.com/BepInEx/BepInEx) for your game. IL2CPP currently requires a [Bleeding Edge](https://builds.bepis.io/projects/bepinex_be) release.
|
||||
2. Download the UnityExplorer release for BepInEx IL2CPP or Mono above.
|
||||
3. Take the `UnityExplorer.BIE.___.dll` file and put it in `[GameFolder]\BepInEx\plugins\`
|
||||
4. In IL2CPP, you will need to download the [Unity libs](https://github.com/LavaGang/Unity-Runtime-Libraries) for the game's Unity version and put them in the `BepInEx\unity-libs\` folder.
|
||||
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.
|
||||
|
||||
### MelonLoader
|
||||
<i>Note: BepInEx 6 is obtainable via [BepisBuilds](https://builds.bepis.io/projects/bepinex_be)</i>
|
||||
|
||||
1. Install [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) 0.3.1+ for your game. This version can currently be obtained from [here](https://github.com/LavaGang/MelonLoader/actions).
|
||||
2. Download the UnityExplorer release for MelonLoader IL2CPP or Mono above.
|
||||
3. Take the `UnityExplorer.ML.___.dll` file and put it in the `[GameFolder]\Mods\` folder.
|
||||
## MelonLoader
|
||||
|
||||
### Standalone
|
||||
| Release | IL2CPP | Mono |
|
||||
| ------- | ------ | ---- |
|
||||
| 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.
|
||||
|
||||
## Standalone
|
||||
|
||||
| IL2CPP | Mono |
|
||||
| ------ | ---- |
|
||||
| ✅ [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).
|
||||
|
||||
@ -50,7 +48,7 @@ The standalone release can be used with any injector or loader of your choice, b
|
||||
3. Create an instance of Unity Explorer with `UnityExplorer.ExplorerStandalone.CreateInstance();`
|
||||
4. Optionally subscribe to the `ExplorerStandalone.OnLog` event to handle logging if you wish
|
||||
|
||||
## Features
|
||||
# Features
|
||||
|
||||
<p align="center">
|
||||
<a href="https://raw.githubusercontent.com/sinai-dev/UnityExplorer/master/img/preview.png">
|
||||
@ -60,49 +58,67 @@ The standalone release can be used with any injector or loader of your choice, b
|
||||
|
||||
### Object Explorer
|
||||
|
||||
* Use the <b>Scene Explorer</b> tab to traverse the active scenes, as well as the DontDestroyOnLoad scene and the HideAndDontSave "scene" (assets and hidden objects).
|
||||
* Use the <b>Scene Explorer</b> tab to traverse the active scenes, as well as the DontDestroyOnLoad and HideAndDontSave objects.
|
||||
* The "HideAndDontSave" scene contains objects with that flag, as well as Assets and Resources which are not in any scene but behave the same way.
|
||||
* You can use the Scene Loader to easily load any of the scenes in the build (may not work for Unity 5.X games)
|
||||
* Use the <b>Object Search</b> tab to search for Unity objects (including GameObjects, Components, etc), C# Singletons or Static Classes.
|
||||
* Use the UnityObject search to look for any objects which derive from `UnityEngine.Object`, with optional filters
|
||||
* The singleton search will look for any classes with a typical "Instance" field, and check it for a current value. This may cause unexpected behaviour in some IL2CPP games as we cannot distinguish between true properties and field-properties, so some property accessors will be invoked.
|
||||
|
||||
### Inspector
|
||||
|
||||
The inspector is used to see detailed information on GameObjects (GameObject Inspector), C# objects (Reflection Inspector) and C# classes (Static Inspector).
|
||||
The inspector is used to see detailed information on objects of any type and manipulate their values, as well as to inspect C# Classes with static reflection.
|
||||
|
||||
For the GameObject Inspector, you can edit any of the input fields in the inspector (excluding readonly fields) and press <b>Enter</b> to apply your changes. You can also do this to the GameObject path as a way to change the GameObject's parent. Press the <b>Escape</b> key to cancel your edits.
|
||||
|
||||
In the Reflection Inspectors, automatic updating is not enabled by default, and you must press Apply for any changes you make to take effect.
|
||||
* The <b>GameObject Inspector</b> (tab prefix `[G]`) is used to inspect a `GameObject`, and to see and manipulate its Transform and Components.
|
||||
* You can edit any of the input fields in the inspector (excluding readonly fields) and press <b>Enter</b> to apply your changes. You can also do this to the GameObject path as a way to change the GameObject's parent. Press the <b>Escape</b> key to cancel your edits.
|
||||
* <i>note: When inspecting a GameObject with a Canvas, the transform controls may be overridden by the RectTransform anchors.</i>
|
||||
* The <b>Reflection Inspectors</b> (tab prefix `[R]` and `[S]`) are used for everything else
|
||||
* Automatic updating is not enabled by default, and you must press Apply for any changes you make to take effect.
|
||||
* Press the `▼` button to expand certain values such as strings, enums, lists, dictionaries, some structs, etc
|
||||
* Use the filters at the top to quickly find the members you are looking for
|
||||
* For `Texture2D` objects, there is a `View Texture` button at the top of the inspector which lets you view it and save it as a PNG file. Currently there are no other similar helpers yet, but I may add more at some point for Mesh, Sprite, Material, etc
|
||||
|
||||
### C# Console
|
||||
|
||||
The C# Console uses the `Mono.CSharp.Evaluator` to define temporary classes or run immediate REPL code.
|
||||
* The C# Console uses the `Mono.CSharp.Evaluator` to define temporary classes or run immediate REPL code.
|
||||
* 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.
|
||||
|
||||
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.
|
||||
* To quickly copy the hook into your own project for further development, use the "Log Hook Source" button.
|
||||
|
||||
### Mouse-Inspect
|
||||
|
||||
The "Mouse Inspect" dropdown on the main UnityExplorer navbar 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
|
||||
* The "Mouse Inspect" dropdown on the main UnityExplorer navbar 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
|
||||
|
||||
### Settings
|
||||
|
||||
You can change the settings via the "Options" page of the main menu, or directly from the config file.
|
||||
* 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`
|
||||
|
||||
Depending on the release you are using, the config file will be found at:
|
||||
* BepInEx: `BepInEx\config\com.sinai.unityexplorer.cfg`
|
||||
* MelonLoader: `UserData\MelonPreferences.cfg`
|
||||
* Standalone `{DLL_location}\UnityExplorer\config.ini`
|
||||
# Building
|
||||
|
||||
## Building
|
||||
For Visual Studio:
|
||||
|
||||
Building the project should be straight-forward, the references are all inside the `lib\` folder.
|
||||
0. Clone the repository and run `git submodule update --init --recursive` to get the submodules.
|
||||
1. Open the `src\UnityExplorer.sln` project.
|
||||
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.
|
||||
|
||||
1. Open the `src\UnityExplorer.sln` project in Visual Studio.
|
||||
2. Select `Solution 'UnityExplorer' (1 of 1 project)` in the Solution Explorer panel, and set the <b>Active config</b> property to the version you want to build, then build it. Alternatively, use "Batch Build" and select all releases.
|
||||
3. The DLLs are built to the `Release\` folder in the root of the repository.
|
||||
4. If ILRepack complains about an error, just change the Active config to a different release and then back again. This sometimes happens for the first time you build the project.
|
||||
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):
|
||||
|
||||
## Acknowledgments
|
||||
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.
|
||||
|
||||
# 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.
|
||||
|
Binary file not shown.
Submodule lib/HarmonyX deleted from 64462b3e31
Submodule lib/Il2CppAssemblyUnhollower updated: 0911fdaca6...0099c25069
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.
Binary file not shown.
Binary file not shown.
@ -3,10 +3,11 @@ 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;
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public class CSAutoCompleter : ISuggestionProvider
|
||||
{
|
||||
@ -22,7 +23,6 @@ namespace UnityExplorer.UI.CSConsole
|
||||
AutoCompleteModal.Instance.ReleaseOwnership(this);
|
||||
}
|
||||
|
||||
// Delimiters for completions, notably does not include '.'
|
||||
private readonly HashSet<char> delimiters = new HashSet<char>
|
||||
{
|
||||
'{', '}', ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&', '?'
|
||||
@ -41,7 +41,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
suggestions.Clear();
|
||||
|
||||
int caret = Math.Max(0, Math.Min(InputField.Text.Length - 1, InputField.Component.caretPosition - 1));
|
||||
int start = caret;
|
||||
int startIdx = caret;
|
||||
|
||||
// If the character at the caret index is whitespace or delimiter,
|
||||
// or if the next character (if it exists) is not whitespace,
|
||||
@ -55,17 +55,17 @@ namespace UnityExplorer.UI.CSConsole
|
||||
}
|
||||
|
||||
// get the current composition string (from caret back to last delimiter)
|
||||
while (start > 0)
|
||||
while (startIdx > 0)
|
||||
{
|
||||
start--;
|
||||
char c = InputField.Text[start];
|
||||
if (delimiters.Contains(c))
|
||||
startIdx--;
|
||||
char c = InputField.Text[startIdx];
|
||||
if (delimiters.Contains(c) || char.IsWhiteSpace(c))
|
||||
{
|
||||
start++;
|
||||
startIdx++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
string input = InputField.Text.Substring(start, caret - start + 1);
|
||||
string input = InputField.Text.Substring(startIdx, caret - startIdx + 1);
|
||||
|
||||
// Get MCS completions
|
||||
|
@ -1,18 +1,21 @@
|
||||
using System;
|
||||
using Mono.CSharp;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CSConsole;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.CSConsole;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public static class ConsoleController
|
||||
{
|
||||
@ -34,6 +37,8 @@ namespace UnityExplorer.UI.CSConsole
|
||||
public static bool EnableAutoIndent { get; private set; } = true;
|
||||
public static bool EnableSuggestions { get; private set; } = true;
|
||||
|
||||
internal static string ScriptsFolder => Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Scripts");
|
||||
|
||||
internal static readonly string[] DefaultUsing = new string[]
|
||||
{
|
||||
"System",
|
||||
@ -49,6 +54,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
// Make sure console is supported on this platform
|
||||
try
|
||||
{
|
||||
ResetConsole(false);
|
||||
@ -61,19 +67,41 @@ namespace UnityExplorer.UI.CSConsole
|
||||
return;
|
||||
}
|
||||
|
||||
// Setup console
|
||||
Lexer = new LexerBuilder();
|
||||
Completer = new CSAutoCompleter();
|
||||
|
||||
SetupHelpInteraction();
|
||||
|
||||
Panel.OnInputChanged += OnInputChanged;
|
||||
Panel.InputScroll.OnScroll += OnInputScrolled;
|
||||
Panel.InputScroller.OnScroll += OnInputScrolled;
|
||||
Panel.OnCompileClicked += Evaluate;
|
||||
Panel.OnResetClicked += ResetConsole;
|
||||
Panel.OnHelpDropdownChanged += HelpSelected;
|
||||
Panel.OnAutoIndentToggled += OnToggleAutoIndent;
|
||||
Panel.OnCtrlRToggled += OnToggleCtrlRShortcut;
|
||||
Panel.OnSuggestionsToggled += OnToggleSuggestions;
|
||||
Panel.OnPanelResized += OnInputScrolled;
|
||||
|
||||
// Run startup script
|
||||
try
|
||||
{
|
||||
if (!Directory.Exists(ScriptsFolder))
|
||||
Directory.CreateDirectory(ScriptsFolder);
|
||||
|
||||
var startupPath = Path.Combine(ScriptsFolder, "startup.cs");
|
||||
if (File.Exists(startupPath))
|
||||
{
|
||||
ExplorerCore.Log($"Executing startup script from '{startupPath}'...");
|
||||
var text = File.ReadAllText(startupPath);
|
||||
Input.Text = text;
|
||||
Evaluate();
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception executing startup script: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -149,15 +177,16 @@ namespace UnityExplorer.UI.CSConsole
|
||||
|
||||
try
|
||||
{
|
||||
// Try to "Compile" the code (tries to interpret it as REPL)
|
||||
var evaluation = Evaluator.Compile(input);
|
||||
if (evaluation != null)
|
||||
// Compile the code. If it returned a CompiledMethod, it is REPL.
|
||||
CompiledMethod repl = Evaluator.Compile(input);
|
||||
|
||||
if (repl != null)
|
||||
{
|
||||
// Valid REPL, we have a delegate to the evaluation.
|
||||
try
|
||||
{
|
||||
object ret = null;
|
||||
evaluation.Invoke(ref ret);
|
||||
repl.Invoke(ref ret);
|
||||
var result = ret?.ToString();
|
||||
if (!string.IsNullOrEmpty(result))
|
||||
ExplorerCore.Log($"Invoked REPL, result: {ret}");
|
||||
@ -171,11 +200,9 @@ namespace UnityExplorer.UI.CSConsole
|
||||
}
|
||||
else
|
||||
{
|
||||
// The input was not recognized as an evaluation. Compile the code.
|
||||
// The compiled code was not REPL, so it was a using directive or it defined classes.
|
||||
|
||||
Evaluator.Run(input);
|
||||
|
||||
string output = ScriptEvaluator._textWriter.ToString();
|
||||
string output = Evaluator._textWriter.ToString();
|
||||
var outputSplit = output.Split('\n');
|
||||
if (outputSplit.Length >= 2)
|
||||
output = outputSplit[outputSplit.Length - 2];
|
||||
@ -230,23 +257,32 @@ namespace UnityExplorer.UI.CSConsole
|
||||
previousInput = value;
|
||||
|
||||
if (EnableSuggestions && AutoCompleteModal.CheckEnter(Completer))
|
||||
{
|
||||
OnAutocompleteEnter();
|
||||
}
|
||||
else if (!settingCaretCoroutine)
|
||||
{
|
||||
if (EnableSuggestions)
|
||||
Completer.CheckAutocompletes();
|
||||
|
||||
if (!settingCaretCoroutine)
|
||||
{
|
||||
if (EnableAutoIndent)
|
||||
DoAutoIndent();
|
||||
}
|
||||
|
||||
HighlightVisibleInput();
|
||||
var inStringOrComment = HighlightVisibleInput();
|
||||
|
||||
if (!settingCaretCoroutine)
|
||||
{
|
||||
if (EnableSuggestions)
|
||||
{
|
||||
if (inStringOrComment)
|
||||
AutoCompleteModal.Instance.ReleaseOwnership(Completer);
|
||||
else
|
||||
Completer.CheckAutocompletes();
|
||||
}
|
||||
}
|
||||
|
||||
UpdateCaret(out _);
|
||||
}
|
||||
|
||||
private static float timeOfLastCtrlR;
|
||||
|
||||
public static void Update()
|
||||
{
|
||||
if (SRENotSupported)
|
||||
@ -254,22 +290,24 @@ namespace UnityExplorer.UI.CSConsole
|
||||
|
||||
UpdateCaret(out bool caretMoved);
|
||||
|
||||
if (!settingCaretCoroutine && EnableSuggestions && AutoCompleteModal.CheckEscape(Completer))
|
||||
if (!settingCaretCoroutine && EnableSuggestions)
|
||||
{
|
||||
OnAutocompleteEscaped();
|
||||
return;
|
||||
}
|
||||
if (AutoCompleteModal.CheckEscape(Completer))
|
||||
{
|
||||
OnAutocompleteEscaped();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!settingCaretCoroutine && EnableSuggestions && caretMoved)
|
||||
{
|
||||
AutoCompleteModal.Instance.ReleaseOwnership(Completer);
|
||||
//Completer.CheckAutocompletes();
|
||||
if (caretMoved)
|
||||
AutoCompleteModal.Instance.ReleaseOwnership(Completer);
|
||||
}
|
||||
|
||||
if (EnableCtrlRShortcut
|
||||
&& (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
|
||||
&& InputManager.GetKeyDown(KeyCode.R))
|
||||
&& InputManager.GetKeyDown(KeyCode.R)
|
||||
&& timeOfLastCtrlR.OccuredEarlierThanDefault())
|
||||
{
|
||||
timeOfLastCtrlR = Time.realtimeSinceStartup;
|
||||
Evaluate(Panel.Input.Text);
|
||||
}
|
||||
}
|
||||
@ -305,7 +343,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
var charBot = charTop - CSCONSOLE_LINEHEIGHT;
|
||||
|
||||
var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
|
||||
var viewportMax = viewportMin - Panel.InputScroll.ViewportRect.rect.height;
|
||||
var viewportMax = viewportMin - Panel.InputScroller.ViewportRect.rect.height;
|
||||
|
||||
float diff = 0f;
|
||||
if (charTop > viewportMin)
|
||||
@ -325,18 +363,31 @@ namespace UnityExplorer.UI.CSConsole
|
||||
{
|
||||
settingCaretCoroutine = true;
|
||||
Input.Component.readOnly = true;
|
||||
RuntimeProvider.Instance.StartCoroutine(SetAutocompleteCaretCoro(caretPosition));
|
||||
RuntimeProvider.Instance.StartCoroutine(SetCaretCoroutine(caretPosition));
|
||||
}
|
||||
|
||||
private static IEnumerator SetAutocompleteCaretCoro(int caretPosition)
|
||||
internal static PropertyInfo SelectionGuardProperty => selectionGuardPropInfo ?? GetSelectionGuardPropInfo();
|
||||
|
||||
private static PropertyInfo GetSelectionGuardPropInfo()
|
||||
{
|
||||
selectionGuardPropInfo = typeof(EventSystem).GetProperty("m_SelectionGuard");
|
||||
if (selectionGuardPropInfo == null)
|
||||
selectionGuardPropInfo = typeof(EventSystem).GetProperty("m_selectionGuard");
|
||||
return selectionGuardPropInfo;
|
||||
}
|
||||
|
||||
private static PropertyInfo selectionGuardPropInfo;
|
||||
|
||||
private static IEnumerator SetCaretCoroutine(int caretPosition)
|
||||
{
|
||||
var color = Input.Component.selectionColor;
|
||||
color.a = 0f;
|
||||
Input.Component.selectionColor = color;
|
||||
EventSystem.current.SetSelectedGameObject(null, null);
|
||||
try { EventSystem.current.SetSelectedGameObject(null, null); } catch { }
|
||||
yield return null;
|
||||
|
||||
EventSystem.current.SetSelectedGameObject(Input.UIRoot, null);
|
||||
try { SelectionGuardProperty.SetValue(EventSystem.current, false, null); } catch { }
|
||||
try { EventSystem.current.SetSelectedGameObject(Input.UIRoot, null); } catch { }
|
||||
Input.Component.Select();
|
||||
yield return null;
|
||||
|
||||
@ -351,48 +402,91 @@ namespace UnityExplorer.UI.CSConsole
|
||||
settingCaretCoroutine = false;
|
||||
}
|
||||
|
||||
|
||||
#region Lexer Highlighting
|
||||
|
||||
private static void HighlightVisibleInput()
|
||||
/// <summary>
|
||||
/// Returns true if caret is inside string or comment, false otherwise
|
||||
/// </summary>
|
||||
private static bool HighlightVisibleInput()
|
||||
{
|
||||
int startIdx = 0;
|
||||
int endIdx = Input.Text.Length - 1;
|
||||
int topLine = 0;
|
||||
|
||||
// Calculate visible text if necessary
|
||||
if (Input.Rect.rect.height > Panel.InputScroll.ViewportRect.rect.height)
|
||||
if (string.IsNullOrEmpty(Input.Text))
|
||||
{
|
||||
topLine = -1;
|
||||
int bottomLine = -1;
|
||||
|
||||
// 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 viewportMax = viewportMin - Panel.InputScroll.ViewportRect.rect.height;
|
||||
|
||||
for (int i = 0; i < Input.TextGenerator.lineCount; i++)
|
||||
{
|
||||
var line = Input.TextGenerator.lines[i];
|
||||
// if not set the top line yet, and top of line is below the viewport top
|
||||
if (topLine == -1 && line.topY <= viewportMin)
|
||||
topLine = i;
|
||||
// if bottom of line is below the viewport bottom
|
||||
if ((line.topY - line.height) >= viewportMax)
|
||||
bottomLine = i;
|
||||
}
|
||||
|
||||
topLine = Math.Max(0, topLine - 1);
|
||||
bottomLine = Math.Min(Input.TextGenerator.lineCount - 1, bottomLine + 1);
|
||||
|
||||
startIdx = Input.TextGenerator.lines[topLine].startCharIdx;
|
||||
endIdx = (bottomLine >= Input.TextGenerator.lineCount - 1)
|
||||
? Input.Text.Length - 1
|
||||
: (Input.TextGenerator.lines[bottomLine + 1].startCharIdx - 1);
|
||||
Panel.HighlightText.text = "";
|
||||
Panel.LineNumberText.text = "1";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Calculate the visible lines
|
||||
|
||||
int topLine = -1;
|
||||
int bottomLine = -1;
|
||||
|
||||
// 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 viewportMax = viewportMin - Panel.InputScroller.ViewportRect.rect.height;
|
||||
|
||||
for (int i = 0; i < Input.TextGenerator.lineCount; i++)
|
||||
{
|
||||
var line = Input.TextGenerator.lines[i];
|
||||
// if not set the top line yet, and top of line is below the viewport top
|
||||
if (topLine == -1 && line.topY <= viewportMin)
|
||||
topLine = i;
|
||||
// if bottom of line is below the viewport bottom
|
||||
if ((line.topY - line.height) >= viewportMax)
|
||||
bottomLine = i;
|
||||
}
|
||||
|
||||
topLine = Math.Max(0, topLine - 1);
|
||||
bottomLine = Math.Min(Input.TextGenerator.lineCount - 1, bottomLine + 1);
|
||||
|
||||
int startIdx = Input.TextGenerator.lines[topLine].startCharIdx;
|
||||
int endIdx = (bottomLine >= Input.TextGenerator.lineCount - 1)
|
||||
? Input.Text.Length - 1
|
||||
: (Input.TextGenerator.lines[bottomLine + 1].startCharIdx - 1);
|
||||
|
||||
|
||||
// Highlight the visible text with the LexerBuilder
|
||||
Panel.HighlightText.text = Lexer.BuildHighlightedString(Input.Text, startIdx, endIdx, topLine);
|
||||
|
||||
Panel.HighlightText.text = Lexer.BuildHighlightedString(Input.Text, startIdx, endIdx, topLine, LastCaretPosition, out bool ret);
|
||||
|
||||
// Set the line numbers
|
||||
|
||||
// determine true starting line number (not the same as the cached TextGenerator line numbers)
|
||||
int realStartLine = 0;
|
||||
for (int i = 0; i < startIdx; i++)
|
||||
{
|
||||
if (LexerBuilder.IsNewLine(Input.Text[i]))
|
||||
realStartLine++;
|
||||
}
|
||||
realStartLine++;
|
||||
char lastPrev = '\n';
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// append leading new lines for spacing (no point rendering line numbers we cant see)
|
||||
for (int i = 0; i < topLine; i++)
|
||||
sb.Append('\n');
|
||||
|
||||
// append the displayed line numbers
|
||||
for (int i = topLine; i <= bottomLine; i++)
|
||||
{
|
||||
if (i > 0)
|
||||
lastPrev = Input.Text[Input.TextGenerator.lines[i].startCharIdx - 1];
|
||||
|
||||
// previous line ended with a newline character, this is an actual new line.
|
||||
if (LexerBuilder.IsNewLine(lastPrev))
|
||||
{
|
||||
sb.Append(realStartLine.ToString());
|
||||
realStartLine++;
|
||||
}
|
||||
|
||||
sb.Append('\n');
|
||||
}
|
||||
|
||||
Panel.LineNumberText.text = sb.ToString();
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -526,7 +620,9 @@ If the game was built with Unity's stubbed netstandard 2.0 runtime, you can fix
|
||||
internal const string STARTUP_TEXT = @"<color=#5d8556>// Welcome to the UnityExplorer C# Console!
|
||||
|
||||
// It is recommended to use the Log panel (or a console log window) while using this tool.
|
||||
// Use the Help dropdown to see detailed examples of how to use the console.</color>";
|
||||
// Use the Help dropdown to see detailed examples of how to use the console.
|
||||
|
||||
// To execute a script automatically on startup, put the script at 'UnityExplorer\Scripts\startup.cs'</color>";
|
||||
|
||||
internal const string HELP_USINGS = @"// You can add a using directive to any namespace, but you must compile for it to take effect.
|
||||
// It will remain in effect until you Reset the console.
|
@ -5,14 +5,16 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CSConsole.Lexers;
|
||||
using UnityExplorer.CSConsole.Lexers;
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public struct MatchInfo
|
||||
{
|
||||
public int startIndex;
|
||||
public int endIndex;
|
||||
public bool isStringOrComment;
|
||||
public bool matchToEndOfLine;
|
||||
public string htmlColorTag;
|
||||
}
|
||||
|
||||
@ -82,8 +84,10 @@ namespace UnityExplorer.UI.CSConsole
|
||||
/// <param name="endIdx">The last character you want to highlight</param>
|
||||
/// <param name="leadingLines">The amount of leading empty lines you want before the first character in the return string.</param>
|
||||
/// <returns>A string which contains the amount of leading lines specified, as well as the rich-text highlighted section.</returns>
|
||||
public string BuildHighlightedString(string input, int startIdx, int endIdx, int leadingLines)
|
||||
public string BuildHighlightedString(string input, int startIdx, int endIdx, int leadingLines, int caretIdx, out bool caretInStringOrComment)
|
||||
{
|
||||
caretInStringOrComment = false;
|
||||
|
||||
if (string.IsNullOrEmpty(input) || endIdx <= startIdx)
|
||||
return input;
|
||||
|
||||
@ -105,14 +109,27 @@ namespace UnityExplorer.UI.CSConsole
|
||||
|
||||
// append the highlighted match
|
||||
sb.Append(match.htmlColorTag);
|
||||
|
||||
for (int i = match.startIndex; i <= match.endIndex && i <= currentEndIdx; i++)
|
||||
sb.Append(input[i]);
|
||||
|
||||
sb.Append(SignatureHighlighter.CLOSE_COLOR);
|
||||
|
||||
// update the last unhighlighted start index
|
||||
lastUnhighlighted = match.endIndex + 1;
|
||||
|
||||
int matchEndIdx = match.endIndex;
|
||||
if (match.matchToEndOfLine)
|
||||
{
|
||||
while (input.Length - 1 >= matchEndIdx)
|
||||
{
|
||||
matchEndIdx++;
|
||||
if (IsNewLine(input[matchEndIdx]))
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// check caretIdx to determine inStringOrComment state
|
||||
if (caretIdx >= match.startIndex && (caretIdx <= (matchEndIdx+1) || (caretIdx >= input.Length && matchEndIdx >= input.Length - 1)))
|
||||
caretInStringOrComment = match.isStringOrComment;
|
||||
}
|
||||
|
||||
// Append trailing unhighlighted input
|
||||
@ -150,6 +167,7 @@ namespace UnityExplorer.UI.CSConsole
|
||||
startIndex = startIndex,
|
||||
endIndex = CommittedIndex,
|
||||
htmlColorTag = lexer.ColorTag,
|
||||
isStringOrComment = lexer is StringLexer || lexer is CommentLexer,
|
||||
};
|
||||
break;
|
||||
}
|
@ -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
|
||||
{
|
||||
@ -41,6 +41,10 @@ namespace UnityExplorer.UI.CSConsole.Lexers
|
||||
while (!lexer.EndOfInput && char.IsLetter(lexer.PeekNext()))
|
||||
sb.Append(lexer.Current);
|
||||
|
||||
// next must be whitespace or delimiter
|
||||
if (!lexer.EndOfInput && !(char.IsWhiteSpace(lexer.Current) || lexer.IsDelimiter(lexer.Current)))
|
||||
return false;
|
||||
|
||||
if (keywords.Contains(sb.ToString()))
|
||||
{
|
||||
if (!lexer.EndOfInput)
|
@ -1,8 +1,8 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
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
|
||||
{
|
||||
@ -11,7 +11,7 @@ namespace UnityExplorer.UI.CSConsole.Lexers
|
||||
protected override Color HighlightColor => new Color(0.6f, 0.6f, 0.6f);
|
||||
|
||||
// all symbols are delimiters
|
||||
public override IEnumerable<char> Delimiters => symbols;
|
||||
public override IEnumerable<char> Delimiters => symbols.Where(it => it != '.'); // '.' is not a delimiter, only a separator.
|
||||
|
||||
public static bool IsSymbol(char c) => symbols.Contains(c);
|
||||
|
@ -1,13 +1,13 @@
|
||||
using System;
|
||||
using Mono.CSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Mono.CSharp;
|
||||
|
||||
// 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,19 +1,13 @@
|
||||
using System;
|
||||
using Mono.CSharp;
|
||||
using Mono.CSharp;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
|
||||
/*
|
||||
Welcome to the UnityExplorer C# Console!
|
||||
Use the Help dropdown to see detailed examples of how to use this console.
|
||||
To see your output, use the Log panel or a Console Log window.
|
||||
*/
|
||||
|
||||
namespace UnityExplorer.UI.CSConsole
|
||||
namespace UnityExplorer.CSConsole
|
||||
{
|
||||
public class ScriptInteraction : InteractiveBase
|
||||
{
|
@ -3,20 +3,20 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
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;
|
||||
}
|
@ -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,11 +2,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.UI.IValues;
|
||||
using UnityExplorer.UI.Utility;
|
||||
using UnityExplorer.CacheObject.IValues;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject
|
||||
namespace UnityExplorer.CacheObject
|
||||
{
|
||||
public class CacheKeyValuePair : CacheObjectBase
|
||||
{
|
||||
@ -62,6 +61,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)
|
@ -2,10 +2,10 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.UI.IValues;
|
||||
using UnityExplorer.CacheObject.IValues;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject
|
||||
namespace UnityExplorer.CacheObject
|
||||
{
|
||||
public class CacheListEntry : CacheObjectBase
|
||||
{
|
||||
@ -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;
|
||||
}
|
||||
|
@ -5,18 +5,15 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.UI.ObjectPool;
|
||||
using UnityExplorer.UI.Utility;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
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)));
|
||||
@ -34,6 +31,7 @@ namespace UnityExplorer.UI.CacheObject
|
||||
this.Owner = inspector;
|
||||
this.NameLabelText = SignatureHighlighter.Parse(member.DeclaringType, false, member);
|
||||
this.NameForFiltering = $"{member.DeclaringType.Name}.{member.Name}";
|
||||
this.NameLabelTextRaw = NameForFiltering;
|
||||
}
|
||||
|
||||
public override void ReleasePooledObjects()
|
||||
@ -60,6 +58,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();
|
||||
@ -67,27 +76,15 @@ 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);
|
||||
@ -100,7 +97,6 @@ namespace UnityExplorer.UI.CacheObject
|
||||
cell.EvaluateHolder.SetActive(!ShouldAutoEvaluate);
|
||||
if (!ShouldAutoEvaluate)
|
||||
{
|
||||
//cell.UpdateToggle.gameObject.SetActive(false);
|
||||
cell.EvaluateButton.Component.gameObject.SetActive(true);
|
||||
if (HasArguments)
|
||||
{
|
||||
@ -119,11 +115,6 @@ namespace UnityExplorer.UI.CacheObject
|
||||
if (!Evaluating)
|
||||
RuntimeProvider.Instance.SetColorBlock(cell.EvaluateButton.Component, evalDisabledColor, evalDisabledColor * 1.3f);
|
||||
}
|
||||
//else
|
||||
//{
|
||||
// cell.UpdateToggle.gameObject.SetActive(true);
|
||||
// cell.UpdateToggle.isOn = AutoUpdateWanted;
|
||||
//}
|
||||
|
||||
if (State == ValueState.NotEvaluated && !ShouldAutoEvaluate)
|
||||
{
|
||||
@ -139,7 +130,6 @@ namespace UnityExplorer.UI.CacheObject
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public void OnEvaluateClicked()
|
||||
{
|
||||
if (!HasArguments)
|
||||
@ -245,8 +235,7 @@ namespace UnityExplorer.UI.CacheObject
|
||||
|
||||
var sig = GetSig(member);
|
||||
|
||||
//ExplorerCore.Log($"Trying to cache member {sig}...");
|
||||
//ExplorerCore.Log(member.DeclaringType.FullName + "." + member.Name);
|
||||
//ExplorerCore.Log($"Trying to cache member {sig}... ({member.MemberType})");
|
||||
|
||||
CacheMember cached;
|
||||
Type returnType;
|
||||
@ -311,7 +300,6 @@ namespace UnityExplorer.UI.CacheObject
|
||||
|
||||
cachedSigs.Add(sig);
|
||||
|
||||
//cached.Initialize(_inspector, declaringType, member, returnType);
|
||||
cached.SetFallbackType(returnType);
|
||||
cached.SetInspectorOwner(_inspector, member);
|
||||
|
||||
@ -324,7 +312,8 @@ namespace UnityExplorer.UI.CacheObject
|
||||
}
|
||||
}
|
||||
|
||||
internal static string GetSig(MemberInfo member) => $"{member.DeclaringType.Name}.{member.Name}";
|
||||
internal static string GetSig(MemberInfo member)
|
||||
=> $"{member.DeclaringType.Name}.{member.Name}";
|
||||
|
||||
internal static string GetArgumentString(ParameterInfo[] args)
|
||||
{
|
@ -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 CacheMethod : CacheMember
|
||||
{
|
||||
@ -30,15 +30,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;
|
@ -7,12 +7,12 @@ using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.UI.IValues;
|
||||
using UnityExplorer.UI.ObjectPool;
|
||||
using UnityExplorer.UI.Utility;
|
||||
using UnityExplorer.CacheObject.IValues;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject
|
||||
namespace UnityExplorer.CacheObject
|
||||
{
|
||||
public enum ValueState
|
||||
{
|
||||
@ -47,6 +47,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; }
|
||||
@ -181,8 +182,8 @@ namespace UnityExplorer.UI.CacheObject
|
||||
return ValueState.Enum;
|
||||
else if (type == typeof(Color) || type == typeof(Color32))
|
||||
return ValueState.Color;
|
||||
else if (InteractiveValueStruct.SupportsType(type))
|
||||
return ValueState.ValueStruct;
|
||||
else if (InteractiveValueStruct.SupportsType(type))
|
||||
return ValueState.ValueStruct;
|
||||
else if (ReflectionUtility.IsDictionary(type))
|
||||
return ValueState.Dictionary;
|
||||
else if (!typeof(Transform).IsAssignableFrom(type) && ReflectionUtility.IsEnumerable(type))
|
||||
@ -260,6 +261,8 @@ namespace UnityExplorer.UI.CacheObject
|
||||
public virtual void SetDataToCell(CacheObjectCell cell)
|
||||
{
|
||||
cell.NameLabel.text = NameLabelText;
|
||||
if (cell.HiddenNameLabel != null)
|
||||
cell.HiddenNameLabel.Text = NameLabelTextRaw;
|
||||
cell.ValueLabel.gameObject.SetActive(true);
|
||||
|
||||
cell.SubContentHolder.gameObject.SetActive(SubContentShowWanted);
|
@ -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
|
||||
{
|
||||
@ -28,10 +28,11 @@ namespace UnityExplorer.UI.CacheObject
|
||||
{
|
||||
try
|
||||
{
|
||||
object ret;
|
||||
if (HasArguments)
|
||||
return PropertyInfo.GetValue(DeclaringInstance, this.Evaluator.TryParseArguments());
|
||||
|
||||
var ret = PropertyInfo.GetValue(DeclaringInstance, null);
|
||||
ret = PropertyInfo.GetValue(DeclaringInstance, this.Evaluator.TryParseArguments());
|
||||
else
|
||||
ret = PropertyInfo.GetValue(DeclaringInstance, null);
|
||||
HadException = false;
|
||||
LastException = null;
|
||||
return ret;
|
@ -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,10 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.UI.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public class InteractiveColor : InteractiveValue
|
||||
{
|
@ -2,16 +2,17 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
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.Utility;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public class InteractiveDictionary : InteractiveValue, ICellPoolDataSource<CacheKeyValuePairCell>, ICacheObjectController
|
||||
{
|
||||
@ -21,8 +22,8 @@ namespace UnityExplorer.UI.IValues
|
||||
|
||||
public override bool CanWrite => base.CanWrite && RefIDictionary != null && !RefIDictionary.IsReadOnly;
|
||||
|
||||
public Type KeyType;
|
||||
public Type ValueType;
|
||||
public Type KeysType;
|
||||
public Type ValuesType;
|
||||
public IDictionary RefIDictionary;
|
||||
|
||||
public int ItemCount => cachedEntries.Count;
|
||||
@ -75,23 +76,13 @@ namespace UnityExplorer.UI.IValues
|
||||
else
|
||||
{
|
||||
var type = value.GetActualType();
|
||||
if (type.IsGenericType && type.GetGenericArguments().Length == 2)
|
||||
{
|
||||
KeyType = type.GetGenericArguments()[0];
|
||||
ValueType = type.GetGenericArguments()[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
KeyType = typeof(object);
|
||||
ValueType = typeof(object);
|
||||
}
|
||||
ReflectionUtility.TryGetEntryTypes(type, out KeysType, out ValuesType);
|
||||
|
||||
CacheEntries(value);
|
||||
|
||||
TopLabel.text = $"[{cachedEntries.Count}] {SignatureHighlighter.Parse(type, false)}";
|
||||
}
|
||||
|
||||
|
||||
this.DictScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
@ -116,7 +107,7 @@ namespace UnityExplorer.UI.IValues
|
||||
else
|
||||
cache = cachedEntries[idx];
|
||||
|
||||
cache.SetFallbackType(ValueType);
|
||||
cache.SetFallbackType(ValuesType);
|
||||
cache.SetKey(dictEnumerator.Current.Key);
|
||||
cache.SetValueFromSource(dictEnumerator.Current.Value);
|
||||
|
||||
@ -178,7 +169,6 @@ namespace UnityExplorer.UI.IValues
|
||||
}
|
||||
|
||||
public int AdjustedWidth => (int)UIRect.rect.width - 80;
|
||||
//public int AdjustedKeyWidth => HalfWidth - 50;
|
||||
|
||||
public override void SetLayout()
|
||||
{
|
@ -5,9 +5,10 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.UI.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public class InteractiveEnum : InteractiveValue
|
||||
{
|
@ -2,16 +2,17 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
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.Utility;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public class InteractiveList : InteractiveValue, ICellPoolDataSource<CacheListEntryCell>, ICacheObjectController
|
||||
{
|
||||
@ -19,13 +20,15 @@ namespace UnityExplorer.UI.IValues
|
||||
object ICacheObjectController.Target => this.CurrentOwner.Value;
|
||||
public Type TargetType { get; private set; }
|
||||
|
||||
public override bool CanWrite => base.CanWrite && RefIList != null && !RefIList.IsReadOnly;
|
||||
public override bool CanWrite => base.CanWrite && ((RefIList != null && !RefIList.IsReadOnly) || IsWritableGenericIList);
|
||||
|
||||
public Type EntryType;
|
||||
public IList RefIList;
|
||||
|
||||
public int ItemCount => values.Count;
|
||||
private readonly List<object> values = new List<object>();
|
||||
private bool IsWritableGenericIList;
|
||||
private PropertyInfo genericIndexer;
|
||||
|
||||
public int ItemCount => cachedEntries.Count;
|
||||
private readonly List<CacheListEntry> cachedEntries = new List<CacheListEntry>();
|
||||
|
||||
public ScrollPool<CacheListEntryCell> ListScrollPool { get; private set; }
|
||||
@ -49,7 +52,6 @@ namespace UnityExplorer.UI.IValues
|
||||
private void ClearAndRelease()
|
||||
{
|
||||
RefIList = null;
|
||||
values.Clear();
|
||||
|
||||
foreach (var entry in cachedEntries)
|
||||
{
|
||||
@ -66,18 +68,13 @@ namespace UnityExplorer.UI.IValues
|
||||
if (value == null)
|
||||
{
|
||||
// should never be null
|
||||
if (values.Any())
|
||||
if (cachedEntries.Any())
|
||||
ClearAndRelease();
|
||||
}
|
||||
else
|
||||
{
|
||||
var type = value.GetActualType();
|
||||
if (type.IsGenericType)
|
||||
EntryType = type.GetGenericArguments()[0];
|
||||
else if (type.HasElementType)
|
||||
EntryType = type.GetElementType();
|
||||
else
|
||||
EntryType = typeof(object);
|
||||
ReflectionUtility.TryGetEntryType(type, out EntryType);
|
||||
|
||||
CacheEntries(value);
|
||||
|
||||
@ -92,7 +89,12 @@ namespace UnityExplorer.UI.IValues
|
||||
{
|
||||
RefIList = value as IList;
|
||||
|
||||
values.Clear();
|
||||
// Check if the type implements IList<T> but not IList (ie. Il2CppArrayBase)
|
||||
if (RefIList == null)
|
||||
CheckGenericIList(value);
|
||||
else
|
||||
IsWritableGenericIList = false;
|
||||
|
||||
int idx = 0;
|
||||
|
||||
if (ReflectionUtility.TryGetEnumerator(value, out IEnumerator enumerator))
|
||||
@ -103,8 +105,6 @@ namespace UnityExplorer.UI.IValues
|
||||
{
|
||||
var entry = enumerator.Current;
|
||||
|
||||
values.Add(entry);
|
||||
|
||||
// If list count increased, create new cache entries
|
||||
CacheListEntry cache;
|
||||
if (idx >= cachedEntries.Count)
|
||||
@ -122,9 +122,9 @@ namespace UnityExplorer.UI.IValues
|
||||
}
|
||||
|
||||
// Remove excess cached entries if list count decreased
|
||||
if (cachedEntries.Count > values.Count)
|
||||
if (cachedEntries.Count > idx)
|
||||
{
|
||||
for (int i = cachedEntries.Count - 1; i >= values.Count; i--)
|
||||
for (int i = cachedEntries.Count - 1; i >= idx; i--)
|
||||
{
|
||||
var cache = cachedEntries[i];
|
||||
if (cache.CellView != null)
|
||||
@ -141,14 +141,61 @@ namespace UnityExplorer.UI.IValues
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckGenericIList(object value)
|
||||
{
|
||||
try
|
||||
{
|
||||
var type = value.GetType();
|
||||
if (type.GetInterfaces().Any(it => it.IsGenericType && it.GetGenericTypeDefinition() == typeof(IList<>)))
|
||||
IsWritableGenericIList = !(bool)type.GetProperty("IsReadOnly").GetValue(value, null);
|
||||
else
|
||||
IsWritableGenericIList = false;
|
||||
|
||||
if (IsWritableGenericIList)
|
||||
{
|
||||
// Find the "this[int index]" property.
|
||||
// It might be a private implementation.
|
||||
foreach (var prop in type.GetProperties(ReflectionUtility.FLAGS))
|
||||
{
|
||||
if ((prop.Name == "Item"
|
||||
|| (prop.Name.StartsWith("System.Collections.Generic.IList<") && prop.Name.EndsWith(">.Item")))
|
||||
&& prop.GetIndexParameters() is ParameterInfo[] parameters
|
||||
&& parameters.Length == 1
|
||||
&& parameters[0].ParameterType == typeof(int))
|
||||
{
|
||||
genericIndexer = prop;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (genericIndexer == null)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Failed to find indexer property for IList<T> type '{type.FullName}'!");
|
||||
IsWritableGenericIList = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception processing IEnumerable for IList<T> check: {ex.ReflectionExToString()}");
|
||||
IsWritableGenericIList = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Setting the value of an index to the list
|
||||
|
||||
public void TrySetValueToIndex(object value, int index)
|
||||
{
|
||||
try
|
||||
{
|
||||
//value = value.TryCast(this.EntryType);
|
||||
RefIList[index] = value;
|
||||
if (!IsWritableGenericIList)
|
||||
{
|
||||
RefIList[index] = value;
|
||||
}
|
||||
else
|
||||
{
|
||||
genericIndexer.SetValue(CurrentOwner.Value, value, new object[] { index });
|
||||
}
|
||||
|
||||
var entry = cachedEntries[index];
|
||||
entry.SetValueFromSource(value);
|
@ -6,10 +6,11 @@ using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.UI.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public class InteractiveString : InteractiveValue
|
||||
{
|
@ -4,10 +4,11 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.UI.ObjectPool;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
|
||||
namespace UnityExplorer.UI.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public abstract class InteractiveValue : IPooledObject
|
||||
{
|
@ -5,10 +5,10 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.UI.Utility;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.UI.IValues
|
||||
namespace UnityExplorer.CacheObject.IValues
|
||||
{
|
||||
public class InteractiveValueStruct : InteractiveValue
|
||||
{
|
@ -4,8 +4,9 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.Views
|
||||
namespace UnityExplorer.CacheObject.Views
|
||||
{
|
||||
public class ConfigEntryCell : CacheObjectCell
|
||||
{
|
@ -4,11 +4,12 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.UI.IValues;
|
||||
using UnityExplorer.CacheObject.IValues;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
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.IValues;
|
||||
using UnityExplorer.CacheObject.IValues;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.Views
|
||||
namespace UnityExplorer.CacheObject.Views
|
||||
{
|
||||
public class CacheListEntryCell : CacheObjectCell
|
||||
{
|
@ -4,9 +4,10 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.Views
|
||||
namespace UnityExplorer.CacheObject.Views
|
||||
{
|
||||
public class CacheMemberCell : CacheObjectCell
|
||||
{
|
@ -4,13 +4,12 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.UI.IValues;
|
||||
using UnityExplorer.UI.ObjectPool;
|
||||
using UnityExplorer.UI.Utility;
|
||||
using UnityExplorer.CacheObject.IValues;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.Views
|
||||
namespace UnityExplorer.CacheObject.Views
|
||||
{
|
||||
public abstract class CacheObjectCell : ICell
|
||||
{
|
||||
@ -46,6 +45,7 @@ namespace UnityExplorer.UI.CacheObject.Views
|
||||
public LayoutElement RightGroupLayout;
|
||||
|
||||
public Text NameLabel;
|
||||
public InputFieldRef HiddenNameLabel;
|
||||
public Text TypeLabel;
|
||||
public Text ValueLabel;
|
||||
public Toggle Toggle;
|
||||
@ -117,8 +117,19 @@ 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
|
||||
|
@ -5,11 +5,11 @@ using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.ObjectPool;
|
||||
using UnityExplorer.UI.Utility;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
|
||||
namespace UnityExplorer.UI.CacheObject.Views
|
||||
namespace UnityExplorer.CacheObject.Views
|
||||
{
|
||||
public class EvaluateWidget : IPooledObject
|
||||
{
|
@ -16,17 +16,18 @@ namespace UnityExplorer.Core.Config
|
||||
// See the UnityExplorer.Loader namespace for the implementations.
|
||||
public static ConfigHandler Handler { get; private set; }
|
||||
|
||||
public static ConfigElement<KeyCode> Master_Toggle;
|
||||
public static ConfigElement<KeyCode> Master_Toggle;
|
||||
public static ConfigElement<UIManager.VerticalAnchor> Main_Navbar_Anchor;
|
||||
public static ConfigElement<bool> Force_Unlock_Mouse;
|
||||
public static ConfigElement<KeyCode> Force_Unlock_Toggle;
|
||||
public static ConfigElement<bool> Aggressive_Mouse_Unlock;
|
||||
public static ConfigElement<string> Default_Output_Path;
|
||||
public static ConfigElement<bool> Log_Unity_Debug;
|
||||
public static ConfigElement<bool> Hide_On_Startup;
|
||||
public static ConfigElement<float> Startup_Delay_Time;
|
||||
public static ConfigElement<bool> Force_Unlock_Mouse;
|
||||
public static ConfigElement<KeyCode> Force_Unlock_Toggle;
|
||||
public static ConfigElement<bool> Aggressive_Mouse_Unlock;
|
||||
public static ConfigElement<bool> Disable_EventSystem_Override;
|
||||
public static ConfigElement<string> Default_Output_Path;
|
||||
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;
|
||||
public static ConfigElement<string> Reflection_Signature_Blacklist;
|
||||
|
||||
// internal configs
|
||||
internal static InternalConfigHandler InternalHandler { get; private set; }
|
||||
@ -36,6 +37,7 @@ namespace UnityExplorer.Core.Config
|
||||
public static ConfigElement<string> CSConsoleData;
|
||||
public static ConfigElement<string> OptionsPanelData;
|
||||
public static ConfigElement<string> ConsoleLogData;
|
||||
public static ConfigElement<string> HookManagerData;
|
||||
|
||||
internal static readonly Dictionary<string, IConfigElement> ConfigElements = new Dictionary<string, IConfigElement>();
|
||||
internal static readonly Dictionary<string, IConfigElement> InternalConfigs = new Dictionary<string, IConfigElement>();
|
||||
@ -93,7 +95,11 @@ namespace UnityExplorer.Core.Config
|
||||
KeyCode.None);
|
||||
|
||||
Aggressive_Mouse_Unlock = new ConfigElement<bool>("Aggressive Mouse Unlock",
|
||||
"Use WaitForEndOfFrame to aggressively force the Mouse to be unlocked (requires game restart).",
|
||||
"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);
|
||||
|
||||
Log_Unity_Debug = new ConfigElement<bool>("Log Unity Debug",
|
||||
@ -108,10 +114,11 @@ namespace UnityExplorer.Core.Config
|
||||
"The delay on startup before the UI is created.",
|
||||
1f);
|
||||
|
||||
Reflection_Signature_Blacklist = new ConfigElement<string>("Reflection Signature Blacklist",
|
||||
"Use this to blacklist certain member signatures if they are known to cause a crash or other issues." +
|
||||
"\r\nSeperate signatures with a semicolon ';'.",
|
||||
"DEFAULT");
|
||||
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 'UnityEngine.Camera.main;'",
|
||||
"");
|
||||
|
||||
// Internal configs (panel save data)
|
||||
|
||||
@ -120,6 +127,7 @@ namespace UnityExplorer.Core.Config
|
||||
CSConsoleData = new ConfigElement<string>("CSConsole", "", "", true);
|
||||
OptionsPanelData = new ConfigElement<string>("OptionsPanel", "", "", true);
|
||||
ConsoleLogData = new ConfigElement<string>("ConsoleLog", "", "", true);
|
||||
HookManagerData = new ConfigElement<string>("HookManager", "", "", true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -87,9 +87,6 @@ namespace UnityExplorer.Core.Config
|
||||
foreach (var entry in ConfigManager.InternalConfigs)
|
||||
sec.AddKey(entry.Key, entry.Value.BoxedValue.ToString());
|
||||
|
||||
if (!Directory.Exists(ExplorerCore.Loader.ConfigFolder))
|
||||
Directory.CreateDirectory(ExplorerCore.Loader.ConfigFolder);
|
||||
|
||||
File.WriteAllText(INI_PATH, data.ToString());
|
||||
}
|
||||
|
||||
|
@ -32,16 +32,32 @@ namespace UnityExplorer
|
||||
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>();
|
||||
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;
|
||||
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()
|
||||
@ -54,7 +70,13 @@ namespace UnityExplorer
|
||||
ExplorerCore.FixedUpdate();
|
||||
}
|
||||
|
||||
internal static void OnPostRender(Camera camera)
|
||||
internal void LateUpdate()
|
||||
{
|
||||
if (onPostRenderFailed)
|
||||
OnPostRender(null);
|
||||
}
|
||||
|
||||
internal static void OnPostRender(Camera _)
|
||||
{
|
||||
ExplorerCore.OnPostRender();
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
using System;
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityExplorer.Core.Input;
|
||||
using BF = System.Reflection.BindingFlags;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.UI;
|
||||
using System.Collections;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace UnityExplorer.Core.Input
|
||||
{
|
||||
@ -26,25 +25,26 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
public static bool ShouldActuallyUnlock => UIManager.ShowMenu && Unlock;
|
||||
|
||||
private static CursorLockMode m_lastLockMode;
|
||||
private static bool m_lastVisibleState;
|
||||
private static CursorLockMode lastLockMode;
|
||||
private static bool lastVisibleState;
|
||||
|
||||
private static bool m_currentlySettingCursor = false;
|
||||
|
||||
private static Type CursorType
|
||||
=> m_cursorType
|
||||
?? (m_cursorType = ReflectionUtility.GetTypeByName("UnityEngine.Cursor"));
|
||||
private static Type m_cursorType;
|
||||
private static bool currentlySettingCursor = false;
|
||||
|
||||
public static void Init()
|
||||
{
|
||||
SetupPatches();
|
||||
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();
|
||||
}
|
||||
@ -61,13 +61,13 @@ namespace UnityExplorer.Core.Input
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly WaitForEndOfFrame _waitForEndOfFrame = new WaitForEndOfFrame();
|
||||
private static WaitForEndOfFrame _waitForEndOfFrame = new WaitForEndOfFrame();
|
||||
|
||||
private static IEnumerator AggressiveUnlockCoroutine()
|
||||
{
|
||||
while (true)
|
||||
{
|
||||
yield return _waitForEndOfFrame;
|
||||
yield return _waitForEndOfFrame ?? (_waitForEndOfFrame = new WaitForEndOfFrame());
|
||||
|
||||
if (UIManager.ShowMenu)
|
||||
UpdateCursorControl();
|
||||
@ -78,26 +78,26 @@ namespace UnityExplorer.Core.Input
|
||||
{
|
||||
try
|
||||
{
|
||||
m_currentlySettingCursor = true;
|
||||
currentlySettingCursor = true;
|
||||
|
||||
if (ShouldActuallyUnlock)
|
||||
{
|
||||
Cursor.lockState = CursorLockMode.None;
|
||||
Cursor.visible = true;
|
||||
|
||||
if (UIManager.EventSys)
|
||||
if (!ConfigManager.Disable_EventSystem_Override.Value && UIManager.EventSys)
|
||||
SetEventSystem();
|
||||
}
|
||||
else
|
||||
{
|
||||
Cursor.lockState = m_lastLockMode;
|
||||
Cursor.visible = m_lastVisibleState;
|
||||
Cursor.lockState = lastLockMode;
|
||||
Cursor.visible = lastVisibleState;
|
||||
|
||||
if (UIManager.EventSys)
|
||||
if (!ConfigManager.Disable_EventSystem_Override.Value && UIManager.EventSys)
|
||||
ReleaseEventSystem();
|
||||
}
|
||||
|
||||
m_currentlySettingCursor = false;
|
||||
currentlySettingCursor = false;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
@ -107,9 +107,9 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
// Event system overrides
|
||||
|
||||
private static bool m_settingEventSystem;
|
||||
private static EventSystem m_lastEventSystem;
|
||||
private static BaseInputModule m_lastInputModule;
|
||||
private static bool settingEventSystem;
|
||||
private static EventSystem lastEventSystem;
|
||||
private static BaseInputModule lastInputModule;
|
||||
|
||||
public static void SetEventSystem()
|
||||
{
|
||||
@ -118,16 +118,16 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
if (EventSystem.current && EventSystem.current != UIManager.EventSys)
|
||||
{
|
||||
m_lastEventSystem = EventSystem.current;
|
||||
m_lastEventSystem.enabled = false;
|
||||
lastEventSystem = EventSystem.current;
|
||||
lastEventSystem.enabled = false;
|
||||
}
|
||||
|
||||
// Set to our current system
|
||||
m_settingEventSystem = true;
|
||||
settingEventSystem = true;
|
||||
UIManager.EventSys.enabled = true;
|
||||
EventSystem.current = UIManager.EventSys;
|
||||
InputManager.ActivateUIModule();
|
||||
m_settingEventSystem = false;
|
||||
settingEventSystem = false;
|
||||
}
|
||||
|
||||
public static void ReleaseEventSystem()
|
||||
@ -135,53 +135,125 @@ namespace UnityExplorer.Core.Input
|
||||
if (InputManager.CurrentType == InputType.InputSystem)
|
||||
return;
|
||||
|
||||
if (m_lastEventSystem && m_lastEventSystem.gameObject.activeSelf)
|
||||
if (lastEventSystem && lastEventSystem.gameObject.activeSelf)
|
||||
{
|
||||
m_lastEventSystem.enabled = true;
|
||||
lastEventSystem.enabled = true;
|
||||
|
||||
m_settingEventSystem = true;
|
||||
EventSystem.current = m_lastEventSystem;
|
||||
m_lastInputModule?.ActivateModule();
|
||||
m_settingEventSystem = false;
|
||||
settingEventSystem = true;
|
||||
EventSystem.current = lastEventSystem;
|
||||
lastInputModule?.ActivateModule();
|
||||
settingEventSystem = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Patches
|
||||
|
||||
private static void SetupPatches()
|
||||
public static void SetupPatches()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (CursorType == null)
|
||||
throw new Exception("Could not load Type 'UnityEngine.Cursor'!");
|
||||
PrefixPropertySetter(typeof(Cursor),
|
||||
"lockState",
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_lockState))));
|
||||
|
||||
// Get current cursor state and enable cursor
|
||||
m_lastLockMode = (CursorLockMode?)CursorType.GetProperty("lockState", BF.Public | BF.Static)?.GetValue(null, null)
|
||||
?? CursorLockMode.None;
|
||||
PrefixPropertySetter(typeof(Cursor),
|
||||
"visible",
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_visible))));
|
||||
|
||||
m_lastVisibleState = (bool?)CursorType.GetProperty("visible", BF.Public | BF.Static)?.GetValue(null, null)
|
||||
?? false;
|
||||
PrefixPropertySetter(typeof(EventSystem),
|
||||
"current",
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_set_current))));
|
||||
|
||||
ExplorerCore.Loader.SetupCursorPatches();
|
||||
PrefixMethod(typeof(EventSystem),
|
||||
"SetSelectedGameObject",
|
||||
// some games use a modified version of uGUI that includes this extra int argument on this method.
|
||||
new Type[] { typeof(GameObject), typeof(BaseEventData), typeof(int) },
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_SetSelectedGameObject))),
|
||||
// most games use these arguments, we'll use them as our "backup".
|
||||
new Type[] { typeof(GameObject), typeof(BaseEventData) });
|
||||
|
||||
//// Not sure if this one is needed.
|
||||
//PrefixMethod(typeof(PointerInputModule),
|
||||
// "ClearSelection",
|
||||
// new Type[0],
|
||||
// new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_PointerInputModule_ClearSelection))));
|
||||
}
|
||||
catch (Exception e)
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log($"Error on CursorUnlocker.Init! {e.GetType()}, {e.Message}");
|
||||
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrefixMethod(Type type, string method, Type[] arguments, HarmonyMethod prefix, Type[] backupArgs = null)
|
||||
{
|
||||
try
|
||||
{
|
||||
var methodInfo = type.GetMethod(method, ReflectionUtility.FLAGS, null, arguments, null);
|
||||
if (methodInfo == null)
|
||||
{
|
||||
if (backupArgs != null)
|
||||
methodInfo = type.GetMethod(method, ReflectionUtility.FLAGS, null, backupArgs, null);
|
||||
|
||||
if (methodInfo == null)
|
||||
throw new MissingMethodException($"Could not find method for patching - '{type.FullName}.{method}'!");
|
||||
}
|
||||
|
||||
var processor = ExplorerCore.Harmony.CreateProcessor(methodInfo);
|
||||
processor.AddPrefix(prefix);
|
||||
processor.Patch();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Unable to patch {type.Name}.{method}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private static void PrefixPropertySetter(Type type, string property, HarmonyMethod prefix)
|
||||
{
|
||||
try
|
||||
{
|
||||
var processor = ExplorerCore.Harmony.CreateProcessor(type.GetProperty(property, ReflectionUtility.FLAGS).GetSetMethod());
|
||||
processor.AddPrefix(prefix);
|
||||
processor.Patch();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log($"Unable to patch {type.Name}.set_{property}: {e.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
// Prevent setting non-UnityExplorer objects as selected when menu is open
|
||||
|
||||
public static bool Prefix_EventSystem_SetSelectedGameObject(GameObject __0)
|
||||
{
|
||||
if (!UIManager.ShowMenu || !UIManager.CanvasRoot)
|
||||
return true;
|
||||
|
||||
return __0 && __0.transform.root.gameObject.GetInstanceID() == UIManager.CanvasRoot.GetInstanceID();
|
||||
}
|
||||
|
||||
//public static bool Prefix_PointerInputModule_ClearSelection()
|
||||
//{
|
||||
// return !(UIManager.ShowMenu && UIManager.CanvasRoot);
|
||||
//}
|
||||
|
||||
// Force EventSystem.current to be UnityExplorer's when menu is open
|
||||
|
||||
public static void Prefix_EventSystem_set_current(ref EventSystem value)
|
||||
{
|
||||
if (!m_settingEventSystem && value != UIManager.EventSys)
|
||||
if (!settingEventSystem && value)
|
||||
{
|
||||
m_lastEventSystem = value;
|
||||
m_lastInputModule = value?.currentInputModule;
|
||||
lastEventSystem = value;
|
||||
lastInputModule = value.currentInputModule;
|
||||
}
|
||||
|
||||
if (ShouldActuallyUnlock)
|
||||
{
|
||||
value = UIManager.EventSys;
|
||||
value.enabled = true;
|
||||
}
|
||||
if (!UIManager.EventSys)
|
||||
return;
|
||||
|
||||
if (!settingEventSystem && ShouldActuallyUnlock && !ConfigManager.Disable_EventSystem_Override.Value)
|
||||
{
|
||||
value = UIManager.EventSys;
|
||||
value.enabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,9 +263,9 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
public static void Prefix_set_lockState(ref CursorLockMode value)
|
||||
{
|
||||
if (!m_currentlySettingCursor)
|
||||
if (!currentlySettingCursor)
|
||||
{
|
||||
m_lastLockMode = value;
|
||||
lastLockMode = value;
|
||||
|
||||
if (ShouldActuallyUnlock)
|
||||
value = CursorLockMode.None;
|
||||
@ -202,9 +274,9 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
public static void Prefix_set_visible(ref bool value)
|
||||
{
|
||||
if (!m_currentlySettingCursor)
|
||||
if (!currentlySettingCursor)
|
||||
{
|
||||
m_lastVisibleState = value;
|
||||
lastVisibleState = value;
|
||||
|
||||
if (ShouldActuallyUnlock)
|
||||
value = true;
|
||||
|
@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
|
||||
namespace UnityExplorer.Core.Input
|
||||
@ -61,7 +61,7 @@ namespace UnityExplorer.Core.Input
|
||||
// First, just try to use the legacy input, see if its working.
|
||||
// The InputSystem package may be present but not actually activated, so we can find out this way.
|
||||
|
||||
if (LegacyInput.TInput != null || (ReflectionUtility.LoadModule("UnityEngine.InputLegacyModule") && LegacyInput.TInput != null))
|
||||
if (LegacyInput.TInput != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -80,7 +80,7 @@ namespace UnityExplorer.Core.Input
|
||||
}
|
||||
}
|
||||
|
||||
if (InputSystem.TKeyboard != null || (ReflectionUtility.LoadModule("Unity.InputSystem") && InputSystem.TKeyboard != null))
|
||||
if (InputSystem.TKeyboard != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
|
@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityExplorer.UI;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace UnityExplorer.Core.Input
|
||||
{
|
||||
@ -12,6 +12,8 @@ namespace UnityExplorer.Core.Input
|
||||
{
|
||||
public InputSystem()
|
||||
{
|
||||
SetupSupportedDevices();
|
||||
|
||||
m_kbCurrentProp = TKeyboard.GetProperty("current");
|
||||
m_kbIndexer = TKeyboard.GetProperty("Item", new Type[] { TKey });
|
||||
|
||||
@ -32,7 +34,37 @@ namespace UnityExplorer.Core.Input
|
||||
.GetMethod("ReadValue");
|
||||
}
|
||||
|
||||
#region reflection cache
|
||||
internal static void SetupSupportedDevices()
|
||||
{
|
||||
try
|
||||
{
|
||||
// typeof(InputSystem)
|
||||
Type TInputSystem = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputSystem");
|
||||
// InputSystem.settings
|
||||
var settings = TInputSystem.GetProperty("settings", BindingFlags.Public | BindingFlags.Static).GetValue(null, null);
|
||||
// typeof(InputSettings)
|
||||
Type TSettings = settings.GetActualType();
|
||||
// InputSettings.supportedDevices
|
||||
PropertyInfo supportedProp = TSettings.GetProperty("supportedDevices", BindingFlags.Public | BindingFlags.Instance);
|
||||
var supportedDevices = supportedProp.GetValue(settings, null);
|
||||
// An empty supportedDevices list means all devices are supported.
|
||||
#if CPP
|
||||
// weird hack for il2cpp, use the implicit operator and cast Il2CppStringArray to ReadOnlyArray<string>
|
||||
var args = new object[] { new UnhollowerBaseLib.Il2CppStringArray(0) };
|
||||
var method = supportedDevices.GetActualType().GetMethod("op_Implicit", BindingFlags.Static | BindingFlags.Public);
|
||||
supportedProp.SetValue(settings, method.Invoke(null, args), null);
|
||||
#else
|
||||
supportedProp.SetValue(settings, Activator.CreateInstance(supportedDevices.GetActualType(), new object[] { new string[0] }), null);
|
||||
#endif
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception setting up InputSystem.settings.supportedDevices list!");
|
||||
ExplorerCore.Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
#region reflection cache
|
||||
|
||||
public static Type TKeyboard => m_tKeyboard ?? (m_tKeyboard = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Keyboard"));
|
||||
private static Type m_tKeyboard;
|
||||
@ -73,7 +105,7 @@ namespace UnityExplorer.Core.Input
|
||||
private static object m_scrollInfo;
|
||||
private static PropertyInfo m_scrollDeltaProp;
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
public Vector2 MousePosition
|
||||
{
|
||||
@ -138,6 +170,8 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
public bool GetMouseButtonDown(int btn)
|
||||
{
|
||||
if (CurrentMouse == null)
|
||||
return false;
|
||||
switch (btn)
|
||||
{
|
||||
case 0: return (bool)m_btnWasPressedProp.GetValue(LeftMouseButton, null);
|
||||
@ -149,6 +183,8 @@ namespace UnityExplorer.Core.Input
|
||||
|
||||
public bool GetMouseButton(int btn)
|
||||
{
|
||||
if (CurrentMouse == null)
|
||||
return false;
|
||||
switch (btn)
|
||||
{
|
||||
case 0: return (bool)m_btnIsPressedProp.GetValue(LeftMouseButton, null);
|
||||
|
@ -31,9 +31,6 @@ namespace UnityExplorer
|
||||
}
|
||||
}
|
||||
|
||||
public static HashSet<Type> GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric)
|
||||
=> ReflectionUtility.GetImplementationsOf(baseType, allowAbstract, allowGeneric);
|
||||
|
||||
// ------- Misc extensions --------
|
||||
|
||||
/// <summary>
|
||||
@ -99,8 +96,10 @@ namespace UnityExplorer
|
||||
|
||||
public static Exception GetInnerMostException(this Exception e)
|
||||
{
|
||||
while (e.InnerException != null)
|
||||
while (e != null)
|
||||
{
|
||||
if (e.InnerException == null)
|
||||
break;
|
||||
#if CPP
|
||||
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException)
|
||||
break;
|
||||
|
@ -14,7 +14,8 @@ 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
|
||||
{
|
||||
@ -24,13 +25,18 @@ namespace UnityExplorer
|
||||
{
|
||||
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
|
||||
#region IL2CPP Extern and pointers
|
||||
|
||||
// Extern C++ methods
|
||||
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
@ -43,23 +49,23 @@ namespace UnityExplorer
|
||||
|
||||
public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr)
|
||||
{
|
||||
if (cppClassPointers.TryGetValue(type.AssemblyQualifiedName, out il2cppPtr))
|
||||
return il2cppPtr != IntPtr.Zero;
|
||||
|
||||
il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
|
||||
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);
|
||||
cppClassPointers.Add(type.AssemblyQualifiedName, il2cppPtr);
|
||||
}
|
||||
|
||||
return il2cppPtr != IntPtr.Zero;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
|
||||
#region Deobfuscation cache
|
||||
#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>();
|
||||
@ -71,24 +77,25 @@ namespace UnityExplorer
|
||||
foreach (var type in asm.TryGetTypes())
|
||||
TryCacheDeobfuscatedType(type);
|
||||
}
|
||||
|
||||
if (DeobfuscatedTypes.Count > 0)
|
||||
ExplorerCore.Log($"Built IL2CPP deobfuscation cache, initial count: {DeobfuscatedTypes.Count}");
|
||||
}
|
||||
|
||||
private static void TryCacheDeobfuscatedType(Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Thanks to Slaynash for this
|
||||
if (type.CustomAttributes.Any(it => it.AttributeType.Name == "ObfuscatedNameAttribute"))
|
||||
{
|
||||
var cppType = Il2CppType.From(type);
|
||||
if (!type.CustomAttributes.Any())
|
||||
return;
|
||||
|
||||
if (!DeobfuscatedTypes.ContainsKey(cppType.FullName))
|
||||
foreach (var att in type.CustomAttributes)
|
||||
{
|
||||
// Thanks to Slaynash for this
|
||||
|
||||
if (att.AttributeType == typeof(ObfuscatedNameAttribute))
|
||||
{
|
||||
DeobfuscatedTypes.Add(cppType.FullName, type);
|
||||
reverseDeobCache.Add(type.FullName, cppType.FullName);
|
||||
string obfuscatedName = att.ConstructorArguments[0].Value.ToString();
|
||||
|
||||
DeobfuscatedTypes.Add(obfuscatedName, type);
|
||||
reverseDeobCache.Add(type.FullName, obfuscatedName);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -103,7 +110,7 @@ namespace UnityExplorer
|
||||
return theString;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
|
||||
// Get type by name
|
||||
@ -116,7 +123,7 @@ namespace UnityExplorer
|
||||
return base.Internal_GetTypeByName(fullName);
|
||||
}
|
||||
|
||||
#region Get actual type
|
||||
#region Get actual type
|
||||
|
||||
internal override Type Internal_GetActualType(object obj)
|
||||
{
|
||||
@ -124,9 +131,11 @@ namespace UnityExplorer
|
||||
return null;
|
||||
|
||||
var type = obj.GetType();
|
||||
|
||||
try
|
||||
{
|
||||
if (type.IsGenericType)
|
||||
return type;
|
||||
|
||||
if (IsString(obj))
|
||||
return typeof(string);
|
||||
|
||||
@ -171,14 +180,15 @@ namespace UnityExplorer
|
||||
if (fullname.StartsWith("System."))
|
||||
fullname = $"Il2Cpp{fullname}";
|
||||
|
||||
AllTypes.TryGetValue(fullname, out Type monoType);
|
||||
if (!AllTypes.TryGetValue(fullname, out Type monoType))
|
||||
ExplorerCore.LogWarning($"Failed to get type by name '{fullname}'!");
|
||||
return monoType;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
|
||||
#region Casting
|
||||
#region Casting
|
||||
|
||||
private static readonly Dictionary<string, IntPtr> cppClassPointers = new Dictionary<string, IntPtr>();
|
||||
|
||||
@ -208,7 +218,7 @@ namespace UnityExplorer
|
||||
// from other structs to il2cpp object
|
||||
else if (typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
|
||||
{
|
||||
return BoxIl2CppObject(obj);
|
||||
return BoxIl2CppObject(obj).TryCast(castTo);
|
||||
}
|
||||
else
|
||||
return obj;
|
||||
@ -232,11 +242,11 @@ namespace UnityExplorer
|
||||
else if (castTo == typeof(string))
|
||||
return UnboxString(obj);
|
||||
|
||||
// Casting from il2cpp object to il2cpp object...
|
||||
|
||||
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))
|
||||
@ -258,10 +268,22 @@ namespace UnityExplorer
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
//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
|
||||
#region Boxing and unboxing ValueTypes
|
||||
|
||||
// cached il2cpp unbox methods
|
||||
internal static readonly Dictionary<string, MethodInfo> unboxMethods = new Dictionary<string, MethodInfo>();
|
||||
@ -275,7 +297,27 @@ namespace UnityExplorer
|
||||
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;
|
||||
|
||||
@ -364,10 +406,10 @@ namespace UnityExplorer
|
||||
return cppStruct;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
|
||||
#region String boxing/unboxing
|
||||
#region String boxing/unboxing
|
||||
|
||||
private const string IL2CPP_STRING_FULLNAME = "Il2CppSystem.String";
|
||||
private const string STRING_FULLNAME = "System.String";
|
||||
@ -408,10 +450,10 @@ namespace UnityExplorer
|
||||
return s;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
|
||||
#region Singleton finder
|
||||
#region Singleton finder
|
||||
|
||||
internal override void Internal_FindSingleton(string[] possibleNames, Type type, BF flags, List<object> instances)
|
||||
{
|
||||
@ -433,76 +475,43 @@ namespace UnityExplorer
|
||||
base.Internal_FindSingleton(possibleNames, type, flags, instances);
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
|
||||
#region Force-loading game modules
|
||||
#region Force-loading game modules
|
||||
|
||||
// Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded.
|
||||
|
||||
internal override bool Internal_LoadModule(string moduleName)
|
||||
{
|
||||
if (!moduleName.EndsWith(".dll", StringComparison.InvariantCultureIgnoreCase))
|
||||
moduleName += ".dll";
|
||||
#if ML
|
||||
var path = Path.Combine("MelonLoader", "Managed", $"{moduleName}");
|
||||
#else
|
||||
var path = Path.Combine("BepInEx", "unhollowed", $"{moduleName}");
|
||||
#endif
|
||||
return DoLoadModule(path);
|
||||
}
|
||||
|
||||
// Force loading all il2cpp modules
|
||||
|
||||
internal void TryLoadGameModules()
|
||||
{
|
||||
string dirpath =
|
||||
#if ML
|
||||
Path.Combine("MelonLoader", "Managed");
|
||||
#elif BIE
|
||||
Path.Combine("BepInEx", "unhollowed");
|
||||
#else
|
||||
Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Modules");
|
||||
#endif
|
||||
;
|
||||
|
||||
if (Directory.Exists(dirpath))
|
||||
var dir = ExplorerCore.Loader.UnhollowedModulesFolder;
|
||||
if (Directory.Exists(dir))
|
||||
{
|
||||
var files = Directory.GetFiles(dirpath);
|
||||
foreach (var filePath in files)
|
||||
{
|
||||
var name = Path.GetFileName(filePath);
|
||||
if (!name.StartsWith("Unity") && !name.StartsWith("Assembly-CSharp"))
|
||||
continue;
|
||||
try
|
||||
{
|
||||
DoLoadModule(filePath, true);
|
||||
}
|
||||
catch //(Exception ex)
|
||||
{
|
||||
//ExplorerCore.LogWarning($"Failed to force-load module '{name}': {ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
foreach (var filePath in Directory.GetFiles(dir, "*.dll"))
|
||||
DoLoadModule(filePath);
|
||||
}
|
||||
else
|
||||
ExplorerCore.LogWarning($"Expected Unhollowed folder path does not exist: '{dir}'. " +
|
||||
$"If you are using the standalone release, you can specify the Unhollowed modules path when you call CreateInstance().");
|
||||
}
|
||||
|
||||
internal bool DoLoadModule(string fullPath, bool suppressWarning = false)
|
||||
internal bool DoLoadModule(string fullPath)
|
||||
{
|
||||
if (!File.Exists(fullPath))
|
||||
if (string.IsNullOrEmpty(fullPath) || !File.Exists(fullPath))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
Assembly.Load(File.ReadAllBytes(fullPath));
|
||||
Assembly.LoadFile(fullPath);
|
||||
return true;
|
||||
}
|
||||
catch (Exception e)
|
||||
catch //(Exception e)
|
||||
{
|
||||
if (!suppressWarning)
|
||||
Console.WriteLine($"Failed loading module '{Path.GetFileName(fullPath)}'! {e.ReflectionExToString()}");
|
||||
//ExplorerCore.LogWarning($"Failed loading module '{Path.GetFileName(fullPath)}'! {e.ReflectionExToString()}");
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -510,7 +519,7 @@ namespace UnityExplorer
|
||||
|
||||
#region Il2cpp reflection blacklist
|
||||
|
||||
public override string DefaultReflectionBlacklist => string.Join(";", defaultIl2CppBlacklist);
|
||||
public override string[] DefaultReflectionBlacklist => defaultIl2CppBlacklist.ToArray();
|
||||
|
||||
// These methods currently cause a crash in most il2cpp games,
|
||||
// even from doing "GetParameters()" on the MemberInfo.
|
||||
@ -552,6 +561,7 @@ namespace UnityExplorer
|
||||
"UnityEngine.Audio.AudioMixerPlayable.Create",
|
||||
"UnityEngine.BoxcastCommand.ScheduleBatch",
|
||||
"UnityEngine.Camera.CalculateProjectionMatrixFromPhysicalProperties",
|
||||
"UnityEngine.Canvas.renderingDisplaySize",
|
||||
"UnityEngine.CapsulecastCommand.ScheduleBatch",
|
||||
"UnityEngine.Collider2D.Cast",
|
||||
"UnityEngine.Collider2D.Raycast",
|
||||
@ -636,6 +646,9 @@ namespace UnityExplorer
|
||||
"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",
|
||||
@ -657,7 +670,51 @@ namespace UnityExplorer
|
||||
#endregion
|
||||
|
||||
|
||||
#region Temp il2cpp list/dictionary fixes
|
||||
#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
|
||||
|
||||
@ -725,21 +782,24 @@ namespace UnityExplorer
|
||||
|
||||
// Some ugly reflection to use the il2cpp interface for the instance type
|
||||
|
||||
var type = list.GetType();
|
||||
var type = list.GetActualType();
|
||||
var key = type.AssemblyQualifiedName;
|
||||
|
||||
if (!getEnumeratorMethods.ContainsKey(key))
|
||||
{
|
||||
getEnumeratorMethods.Add(key, type.GetMethod("GetEnumerator"));
|
||||
var method = type.GetMethod("GetEnumerator")
|
||||
?? type.GetMethod("System_Collections_IEnumerable_GetEnumerator", FLAGS);
|
||||
getEnumeratorMethods.Add(key, method);
|
||||
|
||||
// ensure the enumerator type is supported
|
||||
try
|
||||
{
|
||||
var test = getEnumeratorMethods[key].Invoke(list, null);
|
||||
test.GetType().GetMethod("MoveNext").Invoke(test, null);
|
||||
test.GetActualType().GetMethod("MoveNext").Invoke(test, null);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log($"IEnumerable failed to enumerate: {ex}");
|
||||
notSupportedTypes.Add(key);
|
||||
}
|
||||
}
|
||||
@ -748,7 +808,7 @@ namespace UnityExplorer
|
||||
throw new NotSupportedException($"The IEnumerable type '{type.FullName}' does not support MoveNext.");
|
||||
|
||||
cppEnumerator = getEnumeratorMethods[key].Invoke(list, null);
|
||||
var enumeratorType = cppEnumerator.GetType();
|
||||
var enumeratorType = cppEnumerator.GetActualType();
|
||||
|
||||
var enumInfoKey = enumeratorType.AssemblyQualifiedName;
|
||||
|
||||
@ -802,7 +862,7 @@ namespace UnityExplorer
|
||||
|
||||
try
|
||||
{
|
||||
var type = dictionary.GetType();
|
||||
var type = dictionary.GetActualType();
|
||||
|
||||
if (typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(type))
|
||||
{
|
||||
@ -812,20 +872,23 @@ namespace UnityExplorer
|
||||
|
||||
var keys = type.GetProperty("Keys").GetValue(dictionary, null);
|
||||
|
||||
var keyCollType = keys.GetType();
|
||||
var cacheKey = keys.GetType().AssemblyQualifiedName;
|
||||
var keyCollType = keys.GetActualType();
|
||||
var cacheKey = keyCollType.AssemblyQualifiedName;
|
||||
if (!getEnumeratorMethods.ContainsKey(cacheKey))
|
||||
{
|
||||
getEnumeratorMethods.Add(cacheKey, keyCollType.GetMethod("GetEnumerator"));
|
||||
var method = keyCollType.GetMethod("GetEnumerator")
|
||||
?? keyCollType.GetMethod("System_Collections_IDictionary_GetEnumerator", FLAGS);
|
||||
getEnumeratorMethods.Add(cacheKey, method);
|
||||
|
||||
// test support
|
||||
try
|
||||
{
|
||||
var test = getEnumeratorMethods[cacheKey].Invoke(keys, null);
|
||||
test.GetType().GetMethod("MoveNext").Invoke(test, null);
|
||||
test.GetActualType().GetMethod("MoveNext").Invoke(test, null);
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log($"IDictionary failed to enumerate: {ex}");
|
||||
notSupportedTypes.Add(cacheKey);
|
||||
}
|
||||
}
|
||||
@ -836,16 +899,16 @@ namespace UnityExplorer
|
||||
var keyEnumerator = getEnumeratorMethods[cacheKey].Invoke(keys, null);
|
||||
var keyInfo = new EnumeratorInfo
|
||||
{
|
||||
current = keyEnumerator.GetType().GetProperty("Current"),
|
||||
moveNext = keyEnumerator.GetType().GetMethod("MoveNext"),
|
||||
current = keyEnumerator.GetActualType().GetProperty("Current"),
|
||||
moveNext = keyEnumerator.GetActualType().GetMethod("MoveNext"),
|
||||
};
|
||||
|
||||
var values = type.GetProperty("Values").GetValue(dictionary, null);
|
||||
var valueEnumerator = values.GetType().GetMethod("GetEnumerator").Invoke(values, null);
|
||||
var valueEnumerator = values.GetActualType().GetMethod("GetEnumerator").Invoke(values, null);
|
||||
var valueInfo = new EnumeratorInfo
|
||||
{
|
||||
current = valueEnumerator.GetType().GetProperty("Current"),
|
||||
moveNext = valueEnumerator.GetType().GetMethod("MoveNext"),
|
||||
current = valueEnumerator.GetActualType().GetProperty("Current"),
|
||||
moveNext = valueEnumerator.GetActualType().GetMethod("MoveNext"),
|
||||
};
|
||||
|
||||
dictEnumerator = EnumerateCppDict(keyInfo, keyEnumerator, valueInfo, valueEnumerator);
|
||||
|
57
src/Core/Reflection/Patches.cs
Normal file
57
src/Core/Reflection/Patches.cs
Normal file
@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using HarmonyLib;
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public static class ReflectionPatches
|
||||
{
|
||||
public static void Init()
|
||||
{
|
||||
try
|
||||
{
|
||||
var method = typeof(Assembly).GetMethod(nameof(Assembly.GetTypes), new Type[0]);
|
||||
var processor = ExplorerCore.Harmony.CreateProcessor(method);
|
||||
processor.AddFinalizer(typeof(ReflectionPatches).GetMethod(nameof(Assembly_GetTypes)));
|
||||
processor.Patch();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception setting up Reflection patch: {ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private static readonly Type[] emptyTypes = new Type[0];
|
||||
|
||||
public static Exception Assembly_GetTypes(Assembly __instance, Exception __exception, ref Type[] __result)
|
||||
{
|
||||
if (__exception != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
__result = __instance.GetExportedTypes();
|
||||
}
|
||||
catch (ReflectionTypeLoadException e)
|
||||
{
|
||||
try
|
||||
{
|
||||
__result = e.Types.Where(it => it != null).ToArray();
|
||||
}
|
||||
catch
|
||||
{
|
||||
__result = emptyTypes;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
__result = emptyTypes;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
@ -4,15 +4,14 @@ using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using BF = System.Reflection.BindingFlags;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
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;
|
||||
@ -28,6 +27,8 @@ namespace UnityExplorer
|
||||
new ReflectionUtility();
|
||||
#endif
|
||||
Instance.Initialize();
|
||||
|
||||
ReflectionPatches.Init();
|
||||
}
|
||||
|
||||
protected virtual void Initialize()
|
||||
@ -43,7 +44,7 @@ namespace UnityExplorer
|
||||
public static Action<Type> OnTypeLoaded;
|
||||
|
||||
/// <summary>Key: Type.FullName</summary>
|
||||
public static readonly SortedDictionary<string, Type> AllTypes = new SortedDictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
|
||||
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>();
|
||||
@ -66,10 +67,14 @@ namespace UnityExplorer
|
||||
|
||||
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)
|
||||
@ -84,6 +89,7 @@ namespace UnityExplorer
|
||||
{
|
||||
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);
|
||||
@ -97,16 +103,16 @@ namespace UnityExplorer
|
||||
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);
|
||||
//allTypeNames.Add(type.FullName);
|
||||
}
|
||||
|
||||
// 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
|
||||
@ -151,12 +157,7 @@ namespace UnityExplorer
|
||||
internal virtual string Internal_ProcessTypeInString(string theString, Type type)
|
||||
=> theString;
|
||||
|
||||
// Force loading modules
|
||||
public static bool LoadModule(string moduleName)
|
||||
=> Instance.Internal_LoadModule(moduleName);
|
||||
|
||||
internal virtual bool Internal_LoadModule(string moduleName)
|
||||
=> false;
|
||||
// Singleton finder
|
||||
|
||||
public static void FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances)
|
||||
=> Instance.Internal_FindSingleton(possibleNames, type, flags, instances);
|
||||
@ -220,7 +221,7 @@ namespace UnityExplorer
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
|
||||
#region Type and Generic Parameter implementation cache
|
||||
@ -250,27 +251,25 @@ namespace UnityExplorer
|
||||
/// </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)
|
||||
public static HashSet<Type> GetImplementationsOf(Type baseType, bool allowAbstract, bool allowGeneric, bool allowEnum, bool allowRecursive = true)
|
||||
{
|
||||
var key = GetImplementationKey(baseType);
|
||||
|
||||
int count = AllTypes.Count;
|
||||
HashSet<Type> ret;
|
||||
if (!baseType.IsGenericParameter)
|
||||
ret = GetImplementations(key, baseType, allowAbstract, allowGeneric);
|
||||
ret = GetImplementations(key, baseType, allowAbstract, allowGeneric, allowEnum);
|
||||
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)
|
||||
private static HashSet<Type> GetImplementations(string key, Type baseType, bool allowAbstract, bool allowGeneric, bool allowEnum)
|
||||
{
|
||||
if (!typeInheritance.ContainsKey(key))
|
||||
{
|
||||
@ -286,7 +285,8 @@ namespace UnityExplorer
|
||||
if (set.Contains(type)
|
||||
|| (type.IsAbstract && type.IsSealed) // ignore static classes
|
||||
|| (!allowAbstract && type.IsAbstract)
|
||||
|| (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition)))
|
||||
|| (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition))
|
||||
|| (!allowEnum && type.IsEnum))
|
||||
continue;
|
||||
|
||||
if (type.FullName.Contains("PrivateImplementationDetails")
|
||||
@ -355,7 +355,7 @@ namespace UnityExplorer
|
||||
return genericParameterInheritance[key];
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
|
||||
|
||||
#region Internal MemberInfo Cache
|
||||
@ -432,32 +432,44 @@ namespace UnityExplorer
|
||||
|
||||
#region Reflection Blacklist
|
||||
|
||||
public virtual string DefaultReflectionBlacklist => string.Empty;
|
||||
public virtual string[] DefaultReflectionBlacklist => new string[0];
|
||||
|
||||
public static void LoadBlacklistString(string blacklist)
|
||||
{
|
||||
if (string.Equals(blacklist, "DEFAULT", StringComparison.InvariantCultureIgnoreCase))
|
||||
try
|
||||
{
|
||||
blacklist = Instance.DefaultReflectionBlacklist;
|
||||
ConfigManager.Reflection_Signature_Blacklist.Value = blacklist;
|
||||
ConfigManager.Handler.SaveConfig();
|
||||
return;
|
||||
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;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(blacklist))
|
||||
return;
|
||||
|
||||
var sigs = blacklist.Split(';');
|
||||
foreach (var sig in sigs)
|
||||
catch (Exception ex)
|
||||
{
|
||||
var s = sig.Trim();
|
||||
if (string.IsNullOrEmpty(s))
|
||||
continue;
|
||||
if (!currentBlacklist.Contains(s))
|
||||
currentBlacklist.Add(s);
|
||||
ExplorerCore.LogWarning($"Exception setting up reflection blacklist: {ex.ReflectionExToString()}");
|
||||
}
|
||||
|
||||
Mono.CSharp.IL2CPP.Blacklist.SignatureBlacklist = currentBlacklist;
|
||||
}
|
||||
|
||||
public static bool IsBlacklisted(MemberInfo member)
|
||||
@ -466,6 +478,7 @@ namespace UnityExplorer
|
||||
return false;
|
||||
|
||||
var sig = $"{member.DeclaringType.FullName}.{member.Name}";
|
||||
|
||||
return currentBlacklist.Contains(sig);
|
||||
}
|
||||
|
||||
@ -496,6 +509,39 @@ namespace UnityExplorer
|
||||
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);
|
||||
@ -524,5 +570,28 @@ namespace UnityExplorer
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -32,16 +32,6 @@ namespace UnityExplorer
|
||||
return new AssetBundle(ptr);
|
||||
}
|
||||
|
||||
// static void UnloadAllAssetBundles(bool unloadAllObjects);
|
||||
|
||||
internal delegate void d_UnloadAllAssetBundles(bool unloadAllObjects);
|
||||
|
||||
public static void UnloadAllAssetBundles(bool unloadAllObjects)
|
||||
{
|
||||
var iCall = ICallManager.GetICall<d_UnloadAllAssetBundles>("UnityEngine.AssetBundle::UnloadAllAssetBundles");
|
||||
iCall.Invoke(unloadAllObjects);
|
||||
}
|
||||
|
||||
// ~~~~~~~~~~~~ Instance ~~~~~~~~~~~~
|
||||
|
||||
private readonly IntPtr m_bundlePtr = IntPtr.Zero;
|
||||
@ -78,7 +68,8 @@ namespace UnityExplorer
|
||||
return new UnityEngine.Object(ptr).TryCast<T>();
|
||||
}
|
||||
|
||||
// public extern void Unload(bool unloadAllLoadedObjects);
|
||||
// Unload(bool unloadAllLoadedObjects);
|
||||
|
||||
internal delegate void d_Unload(IntPtr _this, bool unloadAllLoadedObjects);
|
||||
|
||||
public void Unload(bool unloadAssets = true)
|
||||
|
@ -7,7 +7,6 @@ using System.Runtime.InteropServices;
|
||||
|
||||
namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
{
|
||||
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")]
|
||||
public static class ICallManager
|
||||
{
|
||||
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
|
||||
|
@ -6,8 +6,8 @@ using System.Linq;
|
||||
using UnhollowerBaseLib;
|
||||
using UnityEngine;
|
||||
|
||||
// CREDIT HerpDerpenstine
|
||||
// https://github.com/LavaGang/MelonLoader/blob/master/MelonLoader.Support.Il2Cpp/MelonCoroutines.cs
|
||||
// Credit to HerpDerpenstine and knah
|
||||
// https://github.com/LavaGang/MelonLoader/blob/master/SM_Il2Cpp/Coroutines.cs
|
||||
|
||||
namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
{
|
||||
|
@ -22,7 +22,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
public override void Initialize()
|
||||
{
|
||||
ExplorerCore.Context = RuntimeContext.IL2CPP;
|
||||
//Reflection = new Il2CppReflection();
|
||||
TextureUtil = new Il2CppTextureUtil();
|
||||
}
|
||||
|
||||
@ -30,19 +29,12 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
{
|
||||
try
|
||||
{
|
||||
//Application.add_logMessageReceived(new Action<string, string, LogType>(ExplorerCore.Instance.OnUnityLog));
|
||||
|
||||
var logType = ReflectionUtility.GetTypeByName("UnityEngine.Application+LogCallback");
|
||||
var castMethod = logType.GetMethod("op_Implicit", new[] { typeof(Action<string, string, LogType>) });
|
||||
var addMethod = typeof(Application).GetMethod("add_logMessageReceived", BF.Static | BF.Public, null, new[] { logType }, null);
|
||||
addMethod.Invoke(null, new[]
|
||||
{
|
||||
castMethod.Invoke(null, new[] { new Action<string, string, LogType>(Application_logMessageReceived) })
|
||||
});
|
||||
Application.add_logMessageReceived(new Action<string, string, LogType>(Application_logMessageReceived));
|
||||
}
|
||||
catch
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning("Exception setting up Unity log listener, make sure Unity libraries have been unstripped!");
|
||||
ExplorerCore.Log(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@ -51,9 +43,9 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
ExplorerCore.LogUnity(condition, type);
|
||||
}
|
||||
|
||||
public override void StartCoroutine(IEnumerator routine)
|
||||
public override void Update()
|
||||
{
|
||||
Il2CppCoroutine.Start(routine);
|
||||
Il2CppCoroutine.Process();
|
||||
}
|
||||
|
||||
internal override void ProcessOnPostRender()
|
||||
@ -61,9 +53,14 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
Il2CppCoroutine.ProcessWaitForEndOfFrame();
|
||||
}
|
||||
|
||||
public override void Update()
|
||||
internal override void ProcessFixedUpdate()
|
||||
{
|
||||
Il2CppCoroutine.Process();
|
||||
Il2CppCoroutine.ProcessWaitForFixedUpdate();
|
||||
}
|
||||
|
||||
public override void StartCoroutine(IEnumerator routine)
|
||||
{
|
||||
Il2CppCoroutine.Start(routine);
|
||||
}
|
||||
|
||||
public override T AddComponent<T>(GameObject obj, Type type)
|
||||
@ -76,6 +73,7 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
return ScriptableObject.CreateInstance(Il2CppType.From(type));
|
||||
}
|
||||
|
||||
// Pretty disgusting but couldn't figure out a cleaner way yet unfortunately
|
||||
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
|
||||
{
|
||||
var il2cppList = new Il2CppSystem.Collections.Generic.List<RaycastResult>();
|
||||
@ -100,15 +98,17 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
|
||||
internal delegate IntPtr d_FindObjectsOfTypeAll(IntPtr type);
|
||||
|
||||
internal static readonly string[] findObjectsOfTypeAllSignatures = new[]
|
||||
{
|
||||
"UnityEngine.Resources::FindObjectsOfTypeAll",
|
||||
"UnityEngine.ResourcesAPIInternal::FindObjectsOfTypeAll" // Unity 2020+ updated to this
|
||||
};
|
||||
|
||||
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
|
||||
{
|
||||
var iCall = ICallManager.GetICallUnreliable<d_FindObjectsOfTypeAll>(new[]
|
||||
{
|
||||
"UnityEngine.Resources::FindObjectsOfTypeAll",
|
||||
"UnityEngine.ResourcesAPIInternal::FindObjectsOfTypeAll" // Unity 2020+ updated to this
|
||||
});
|
||||
|
||||
return new Il2CppReferenceArray<UnityEngine.Object>(iCall.Invoke(Il2CppType.From(type).Pointer));
|
||||
return new Il2CppReferenceArray<UnityEngine.Object>(
|
||||
ICallManager.GetICallUnreliable<d_FindObjectsOfTypeAll>(findObjectsOfTypeAllSignatures)
|
||||
.Invoke(Il2CppType.From(type).Pointer));
|
||||
}
|
||||
|
||||
// Scene.GetRootGameObjects();
|
||||
@ -120,22 +120,17 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
if (!scene.isLoaded)
|
||||
return new GameObject[0];
|
||||
|
||||
int handle = scene.handle;
|
||||
|
||||
if (handle == -1)
|
||||
if (scene.handle == -1)
|
||||
return new GameObject[0];
|
||||
|
||||
int count = GetRootCount(handle);
|
||||
int count = GetRootCount(scene.handle);
|
||||
|
||||
if (count < 1)
|
||||
return new GameObject[0];
|
||||
|
||||
var list = new Il2CppSystem.Collections.Generic.List<GameObject>(count);
|
||||
|
||||
var iCall = ICallManager.GetICall<d_GetRootGameObjects>("UnityEngine.SceneManagement.Scene::GetRootGameObjectsInternal");
|
||||
|
||||
iCall.Invoke(handle, list.Pointer);
|
||||
|
||||
iCall.Invoke(scene.handle, list.Pointer);
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
|
@ -38,19 +38,6 @@ namespace UnityExplorer.Core.Runtime.Il2Cpp
|
||||
return new Il2CppStructArray<byte>(ptr);
|
||||
}
|
||||
|
||||
// bool ImageConversion.LoadImage(this Texture2D tex, byte[] data, bool markNonReadable);
|
||||
|
||||
internal delegate bool d_LoadImage(IntPtr tex, IntPtr data, bool markNonReadable);
|
||||
|
||||
public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
|
||||
{
|
||||
var il2cppArray = (Il2CppStructArray<byte>)data;
|
||||
|
||||
var iCall = ICallManager.GetICall<d_LoadImage>("UnityEngine.ImageConversion::LoadImage");
|
||||
|
||||
return iCall.Invoke(tex.Pointer, il2cppArray.Pointer, markNonReadable);
|
||||
}
|
||||
|
||||
// Sprite Sprite.Create
|
||||
|
||||
public override Sprite CreateSprite(Texture2D texture)
|
||||
|
@ -10,6 +10,7 @@ using UnityEngine.Events;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer;
|
||||
|
||||
namespace UnityExplorer.Core.Runtime.Mono
|
||||
{
|
||||
@ -18,7 +19,6 @@ namespace UnityExplorer.Core.Runtime.Mono
|
||||
public override void Initialize()
|
||||
{
|
||||
ExplorerCore.Context = RuntimeContext.Mono;
|
||||
//Reflection = new MonoReflection();
|
||||
TextureUtil = new MonoTextureUtil();
|
||||
}
|
||||
|
||||
@ -28,34 +28,23 @@ namespace UnityExplorer.Core.Runtime.Mono
|
||||
}
|
||||
|
||||
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
|
||||
{
|
||||
ExplorerCore.LogUnity(condition, type);
|
||||
}
|
||||
=> ExplorerCore.LogUnity(condition, type);
|
||||
|
||||
public override void StartCoroutine(IEnumerator routine)
|
||||
{
|
||||
ExplorerBehaviour.Instance.StartCoroutine(routine);
|
||||
}
|
||||
=> ExplorerBehaviour.Instance.StartCoroutine(routine);
|
||||
|
||||
public override void Update()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override T AddComponent<T>(GameObject obj, Type type)
|
||||
{
|
||||
return (T)obj.AddComponent(type);
|
||||
}
|
||||
=> (T)obj.AddComponent(type);
|
||||
|
||||
public override ScriptableObject CreateScriptable(Type type)
|
||||
{
|
||||
return ScriptableObject.CreateInstance(type);
|
||||
}
|
||||
=> ScriptableObject.CreateInstance(type);
|
||||
|
||||
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
|
||||
{
|
||||
raycaster.Raycast(data, list);
|
||||
}
|
||||
=> raycaster.Raycast(data, list);
|
||||
|
||||
public override string LayerToName(int layer)
|
||||
=> LayerMask.LayerToName(layer);
|
||||
@ -63,25 +52,11 @@ namespace UnityExplorer.Core.Runtime.Mono
|
||||
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();
|
||||
}
|
||||
=> scene.isLoaded ? scene.GetRootGameObjects() : new GameObject[0];
|
||||
|
||||
public override int GetRootCount(Scene scene)
|
||||
{
|
||||
return scene.rootCount;
|
||||
}
|
||||
=> scene.rootCount;
|
||||
|
||||
public override void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
|
||||
Color? disabled = null)
|
||||
@ -104,58 +79,41 @@ namespace UnityExplorer.Core.Runtime.Mono
|
||||
}
|
||||
|
||||
public override void SetColorBlock(Selectable selectable, ColorBlock colors)
|
||||
{
|
||||
selectable.colors = colors;
|
||||
}
|
||||
=> selectable.colors = colors;
|
||||
}
|
||||
}
|
||||
|
||||
public static class MonoExtensions
|
||||
{
|
||||
// Helpers to use the same style of AddListener that IL2CPP uses.
|
||||
|
||||
public static void AddListener(this UnityEvent _event, Action listener)
|
||||
{
|
||||
_event.AddListener(new UnityAction(listener));
|
||||
}
|
||||
=> _event.AddListener(new UnityAction(listener));
|
||||
|
||||
public static void AddListener<T>(this UnityEvent<T> _event, Action<T> listener)
|
||||
{
|
||||
_event.AddListener(new UnityAction<T>(listener));
|
||||
}
|
||||
=> _event.AddListener(new UnityAction<T>(listener));
|
||||
|
||||
public static void RemoveListener(this UnityEvent _event, Action listener)
|
||||
{
|
||||
_event.RemoveListener(new UnityAction(listener));
|
||||
}
|
||||
=> _event.RemoveListener(new UnityAction(listener));
|
||||
|
||||
public static void RemoveListener<T>(this UnityEvent<T> _event, Action<T> listener)
|
||||
{
|
||||
_event.RemoveListener(new UnityAction<T>(listener));
|
||||
}
|
||||
=> _event.RemoveListener(new UnityAction<T>(listener));
|
||||
|
||||
// Doesn't exist in NET 3.5
|
||||
|
||||
public static void Clear(this StringBuilder sb)
|
||||
{
|
||||
sb.Remove(0, sb.Length);
|
||||
}
|
||||
=> sb.Remove(0, sb.Length);
|
||||
|
||||
private static PropertyInfo pi_childControlHeight;
|
||||
// These properties don't exist in some earlier games, so null check before trying to set them.
|
||||
|
||||
public static void SetChildControlHeight(this HorizontalOrVerticalLayoutGroup group, bool value)
|
||||
{
|
||||
if (pi_childControlHeight == null)
|
||||
pi_childControlHeight = group.GetType().GetProperty("childControlHeight");
|
||||
=> ReflectionUtility.GetPropertyInfo(typeof(HorizontalOrVerticalLayoutGroup), "childControlHeight")
|
||||
?.SetValue(group, value, null);
|
||||
|
||||
pi_childControlHeight?.SetValue(group, value, null);
|
||||
}
|
||||
|
||||
private static PropertyInfo pi_childControlWidth;
|
||||
|
||||
public static void SetChildControlWidth(this HorizontalOrVerticalLayoutGroup group, bool value)
|
||||
{
|
||||
if (pi_childControlWidth == null)
|
||||
pi_childControlWidth = group.GetType().GetProperty("childControlWidth");
|
||||
|
||||
pi_childControlWidth?.SetValue(group, value, null);
|
||||
}
|
||||
=> ReflectionUtility.GetPropertyInfo(typeof(HorizontalOrVerticalLayoutGroup), "childControlWidth")
|
||||
?.SetValue(group, value, null);
|
||||
}
|
||||
|
||||
#endif
|
@ -12,41 +12,28 @@ namespace UnityExplorer.Core.Runtime.Mono
|
||||
public class MonoTextureUtil : TextureUtilProvider
|
||||
{
|
||||
public override void Blit(Texture2D tex, RenderTexture rt)
|
||||
{
|
||||
Graphics.Blit(tex, rt);
|
||||
}
|
||||
=> Graphics.Blit(tex, rt);
|
||||
|
||||
public override Sprite CreateSprite(Texture2D texture)
|
||||
{
|
||||
return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
|
||||
}
|
||||
=> Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
|
||||
|
||||
public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
|
||||
{
|
||||
return tex.LoadImage(data, markNonReadable);
|
||||
}
|
||||
//public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
|
||||
// => tex.LoadImage(data, markNonReadable);
|
||||
|
||||
public override Texture2D NewTexture2D(int width, int height)
|
||||
{
|
||||
return new Texture2D(width, height);
|
||||
}
|
||||
=> new Texture2D(width, height);
|
||||
|
||||
public override byte[] EncodeToPNG(Texture2D tex)
|
||||
{
|
||||
return EncodeToPNGSafe(tex);
|
||||
}
|
||||
=> EncodeToPNGSafe(tex);
|
||||
|
||||
private static MethodInfo EncodeToPNGMethod => m_encodeToPNGMethod ?? GetEncodeToPNGMethod();
|
||||
private static MethodInfo m_encodeToPNGMethod;
|
||||
|
||||
public static byte[] EncodeToPNGSafe(Texture2D tex)
|
||||
{
|
||||
var method = EncodeToPNGMethod;
|
||||
|
||||
if (method.IsStatic)
|
||||
return (byte[])method.Invoke(null, new object[] { tex });
|
||||
else
|
||||
return (byte[])method.Invoke(tex, ArgumentUtility.EmptyArgs);
|
||||
return EncodeToPNGMethod.IsStatic
|
||||
? (byte[])EncodeToPNGMethod.Invoke(null, new object[] { tex })
|
||||
: (byte[])EncodeToPNGMethod.Invoke(tex, ArgumentUtility.EmptyArgs);
|
||||
}
|
||||
|
||||
private static MethodInfo GetEncodeToPNGMethod()
|
||||
|
@ -40,8 +40,6 @@ namespace UnityExplorer
|
||||
|
||||
public abstract void Update();
|
||||
|
||||
//public virtual bool IsReferenceEqual(object a, object b) => ReferenceEquals(a, b);
|
||||
|
||||
// Unity API handlers
|
||||
|
||||
public abstract T AddComponent<T>(GameObject obj, Type type) where T : Component;
|
||||
@ -54,12 +52,13 @@ namespace UnityExplorer
|
||||
|
||||
public abstract void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list);
|
||||
|
||||
//public abstract int GetSceneHandle(Scene scene);
|
||||
|
||||
public abstract GameObject[] GetRootGameObjects(Scene scene);
|
||||
|
||||
public abstract int GetRootCount(Scene scene);
|
||||
|
||||
public void SetColorBlockAuto(Selectable selectable, Color baseColor)
|
||||
=> SetColorBlock(selectable, baseColor, baseColor * 1.2f, baseColor * 0.8f);
|
||||
|
||||
public abstract void SetColorBlock(Selectable selectable, ColorBlock colors);
|
||||
|
||||
public abstract void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
|
||||
|
@ -22,7 +22,7 @@ namespace UnityExplorer.Core.Runtime
|
||||
|
||||
public abstract void Blit(Texture2D tex, RenderTexture rt);
|
||||
|
||||
public abstract bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable);
|
||||
//public abstract bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable);
|
||||
|
||||
public abstract Sprite CreateSprite(Texture2D texture);
|
||||
|
||||
@ -43,27 +43,22 @@ namespace UnityExplorer.Core.Runtime
|
||||
}
|
||||
}
|
||||
|
||||
public static bool LoadImage(Texture2D tex, string filePath, bool markNonReadable)
|
||||
{
|
||||
if (!File.Exists(filePath))
|
||||
return false;
|
||||
|
||||
return Instance.LoadImage(tex, File.ReadAllBytes(filePath), markNonReadable);
|
||||
}
|
||||
//public static bool LoadImage(Texture2D tex, string filePath, bool markNonReadable)
|
||||
//{
|
||||
// if (!File.Exists(filePath))
|
||||
// return false;
|
||||
//
|
||||
// return Instance.LoadImage(tex, File.ReadAllBytes(filePath), markNonReadable);
|
||||
//}
|
||||
|
||||
public static Texture2D Copy(Texture2D orig, Rect rect)
|
||||
{
|
||||
Color[] pixels;
|
||||
|
||||
if (!IsReadable(orig))
|
||||
orig = ForceReadTexture(orig);
|
||||
|
||||
pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
|
||||
|
||||
Color[] pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
|
||||
Texture2D newTex = Instance.NewTexture2D((int)rect.width, (int)rect.height);
|
||||
|
||||
newTex.SetPixels(pixels);
|
||||
|
||||
return newTex;
|
||||
}
|
||||
|
||||
@ -92,7 +87,7 @@ namespace UnityExplorer.Core.Runtime
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log("Exception on ForceReadTexture: " + e.ToString());
|
||||
ExplorerCore.Log($"Exception on ForceReadTexture: {e.ToString()}");
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@ -103,13 +98,11 @@ namespace UnityExplorer.Core.Runtime
|
||||
Directory.CreateDirectory(dir);
|
||||
|
||||
byte[] data;
|
||||
string savepath = dir + @"\" + name + ".png";
|
||||
string savepath = $@"{dir}\{name}.png";
|
||||
|
||||
// Make sure we can EncodeToPNG it.
|
||||
if (tex.format != TextureFormat.ARGB32 || !IsReadable(tex))
|
||||
{
|
||||
tex = ForceReadTexture(tex);
|
||||
}
|
||||
|
||||
if (isDTXnmNormal)
|
||||
{
|
||||
@ -120,13 +113,9 @@ namespace UnityExplorer.Core.Runtime
|
||||
data = Instance.EncodeToPNG(tex);
|
||||
|
||||
if (data == null || !data.Any())
|
||||
{
|
||||
ExplorerCore.LogWarning("Couldn't get any data for the texture!");
|
||||
}
|
||||
else
|
||||
{
|
||||
File.WriteAllBytes(savepath, data);
|
||||
}
|
||||
}
|
||||
|
||||
// Converts DTXnm-format Normal Map to RGBA-format Normal Map.
|
||||
|
@ -2,11 +2,11 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.UI.IValues;
|
||||
using System.Reflection;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.CacheObject.IValues;
|
||||
#if CPP
|
||||
using UnhollowerRuntimeLib;
|
||||
using UnhollowerBaseLib;
|
||||
@ -16,78 +16,29 @@ namespace UnityExplorer.Tests
|
||||
{
|
||||
public static class TestClass
|
||||
{
|
||||
public static void ATestMethod(string s, float f, Vector3 vector, DateTime date, Quaternion quater, bool b, CameraClearFlags enumvalue)
|
||||
static TestClass()
|
||||
{
|
||||
ExplorerCore.Log($"{s}, {f}, {vector.ToString()}, {date}, {quater.eulerAngles.ToString()}, {b}, {enumvalue}");
|
||||
Init_Mono();
|
||||
#if CPP
|
||||
Init_IL2CPP();
|
||||
#endif
|
||||
}
|
||||
|
||||
public static List<int> AWritableList = new List<int> { 1, 2, 3, 4, 5 };
|
||||
public static Dictionary<string, int> AWritableDict = new Dictionary<string, int> { { "one", 1 }, { "two", 2 } };
|
||||
|
||||
public static IEnumerable ANestedList = new List<List<List<string>>>
|
||||
{
|
||||
new List<List<string>>
|
||||
{
|
||||
new List<string>
|
||||
{
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
"one",
|
||||
"two",
|
||||
},
|
||||
new List<string>
|
||||
{
|
||||
"three",
|
||||
"four",
|
||||
}
|
||||
},
|
||||
new List<List<string>>
|
||||
{
|
||||
new List<string>
|
||||
{
|
||||
"five"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public static IDictionary ARandomDictionary = new Dictionary<object, object>
|
||||
{
|
||||
{ 1, 2 },
|
||||
{ "one", "two" },
|
||||
{ true, false },
|
||||
{ new Vector3(0,1,2), new Vector3(1,2,3) },
|
||||
{ CameraClearFlags.Depth, CameraClearFlags.Color },
|
||||
{ "################################################\r\n##########", null },
|
||||
{ "subdict", new Dictionary<object,object> { { "key", "value" } } }
|
||||
};
|
||||
|
||||
public static Hashtable TestHashtable = new Hashtable
|
||||
{
|
||||
{ "one", "value" },
|
||||
{ "two", "value" },
|
||||
{ "three", "value" },
|
||||
};
|
||||
|
||||
public const int ConstantInt = 5;
|
||||
|
||||
public static Color AColor = Color.magenta;
|
||||
public static Color32 AColor32 = Color.red;
|
||||
|
||||
// Test enumerables
|
||||
public static List<object> ListOfInts;
|
||||
public static List<List<List<string>>> NestedList;
|
||||
public static IDictionary MixedDictionary;
|
||||
public static Hashtable Hashtable;
|
||||
public static byte[] ByteArray = new byte[16];
|
||||
public static string LongString = new string('#', 10000);
|
||||
public static List<string> BigList = new List<string>(10000);
|
||||
public static List<short> ABigList = new List<short>(10000);
|
||||
|
||||
// Test const behaviour (should be a readonly field)
|
||||
public const int ConstantInt5 = 5;
|
||||
|
||||
// Testing other InteractiveValues
|
||||
public static Color Color = Color.magenta;
|
||||
public static Color32 Color32 = Color.red;
|
||||
public static string ALongString = new string('#', 10000);
|
||||
|
||||
public static List<object> RandomList
|
||||
{
|
||||
@ -101,25 +52,7 @@ namespace UnityExplorer.Tests
|
||||
}
|
||||
}
|
||||
|
||||
private static void TestGeneric<T>()
|
||||
{
|
||||
ExplorerCore.Log("Test1 " + typeof(T).FullName);
|
||||
}
|
||||
|
||||
private static void TestGenericClass<T>() where T : class
|
||||
{
|
||||
ExplorerCore.Log("Test2 " + typeof(T).FullName);
|
||||
}
|
||||
|
||||
private static void TestComponent<T>() where T : Component
|
||||
{
|
||||
ExplorerCore.Log("Test3 " + typeof(T).FullName);
|
||||
}
|
||||
|
||||
private static void TestStruct<T>() where T : struct
|
||||
{
|
||||
ExplorerCore.Log("Test3 " + typeof(T).FullName);
|
||||
}
|
||||
// Test methods
|
||||
|
||||
private static object GetRandomObject()
|
||||
{
|
||||
@ -133,109 +66,133 @@ namespace UnityExplorer.Tests
|
||||
case 2: return true;
|
||||
case 3: return "hello";
|
||||
case 4: return 50.5f;
|
||||
case 5: return UnityEngine.CameraClearFlags.Color;
|
||||
case 6: return new List<string> { "sub list", "lol" };
|
||||
case 5: return CameraClearFlags.Color;
|
||||
case 6: return new List<string> { "one", "two" };
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static void TestComponent<T>() where T : Component
|
||||
{
|
||||
ExplorerCore.Log($"Test3 {typeof(T).FullName}");
|
||||
}
|
||||
|
||||
public static void TestArgumentParse(string s, int i, Color color, CameraClearFlags flags, Vector3 vector, Quaternion quaternion)
|
||||
{
|
||||
ExplorerCore.Log($"{s}, {i}, {color.ToString()}, {flags}, {vector.ToString()}, {quaternion.ToString()}");
|
||||
}
|
||||
|
||||
private static void Init_Mono()
|
||||
{
|
||||
ExplorerCore.Log($"1: Basic list");
|
||||
ListOfInts = new List<object> { 1, 2, 3, 4, 5 };
|
||||
|
||||
ExplorerCore.Log($"2: Nested list");
|
||||
NestedList = new List<List<List<string>>>
|
||||
{
|
||||
new List<List<string>> {
|
||||
new List<string> { "1", "2", "3" },
|
||||
new List<string> { "4", "5", "6" },
|
||||
},
|
||||
new List<List<string>>
|
||||
{
|
||||
new List<string> { "7", "8", "9" }
|
||||
}
|
||||
};
|
||||
|
||||
ExplorerCore.Log($"3: Dictionary");
|
||||
MixedDictionary = new Dictionary<object, object>
|
||||
{
|
||||
{ 1, 2 },
|
||||
{ "one", "two" },
|
||||
{ true, false },
|
||||
{ new Vector3(0,1,2), new Vector3(1,2,3) },
|
||||
{ CameraClearFlags.Depth, CameraClearFlags.Color },
|
||||
{ "################################################\r\n##########", null },
|
||||
{ "subdict", new Dictionary<object,object> { { "key", "value" } } }
|
||||
};
|
||||
|
||||
ExplorerCore.Log($"4: Hashtable");
|
||||
Hashtable = new Hashtable { { "One", 1 }, { "Two", 2 } };
|
||||
|
||||
ExplorerCore.Log($"5: Big list");
|
||||
for (int i = 0; i < ABigList.Capacity; i++)
|
||||
ABigList.Add((short)UnityEngine.Random.Range(0, short.MaxValue));
|
||||
|
||||
ExplorerCore.Log("Finished TestClass Init_Mono");
|
||||
}
|
||||
|
||||
#if CPP
|
||||
|
||||
public static Il2CppSystem.Collections.IList IL2CPP_IList;
|
||||
public static Il2CppSystem.Collections.Generic.List<string> IL2CPP_ListString;
|
||||
public static Il2CppSystem.Collections.Generic.HashSet<string> IL2CPP_HashSet;
|
||||
|
||||
public static Il2CppSystem.Collections.Generic.Dictionary<string, string> IL2CPP_Dict;
|
||||
public static Il2CppSystem.Collections.Hashtable IL2CPP_HashTable;
|
||||
public static Il2CppSystem.Collections.IDictionary IL2CPP_IDict;
|
||||
|
||||
public static string IL2CPP_systemString = "Test";
|
||||
public static Il2CppSystem.Object IL2CPP_objectString = "string boxed as cpp object";
|
||||
public static Il2CppSystem.String IL2CPP_il2cppString = "string boxed as cpp string";
|
||||
public static string nullString = null;
|
||||
|
||||
public static List<Il2CppSystem.Object> IL2CPP_listOfBoxedObjects;
|
||||
public static Il2CppStructArray<int> IL2CPP_structArray;
|
||||
public static Il2CppStringArray IL2CPP_stringArray;
|
||||
public static Il2CppReferenceArray<Il2CppSystem.Object> IL2CPP_ReferenceArray;
|
||||
public static Il2CppSystem.Collections.IDictionary IL2CPP_IDict;
|
||||
public static Il2CppSystem.Collections.IList IL2CPP_IList;
|
||||
public static Dictionary<Il2CppSystem.String, Il2CppSystem.Object> CppBoxedDict;
|
||||
|
||||
public static Il2CppSystem.Collections.Generic.HashSet<string> IL2CPP_HashSet;
|
||||
public static Il2CppSystem.Collections.Generic.Dictionary<string, string> IL2CPP_Dict;
|
||||
public static Il2CppSystem.Collections.Hashtable IL2CPP_HashTable;
|
||||
public static Il2CppSystem.Object cppBoxedInt;
|
||||
public static Il2CppSystem.Int32 cppInt;
|
||||
public static Il2CppSystem.Decimal cppDecimal;
|
||||
public static Il2CppSystem.Object cppDecimalBoxed;
|
||||
public static Il2CppSystem.Object cppVector3Boxed;
|
||||
public static string IL2CPP_systemString = "Test";
|
||||
public static Il2CppSystem.Object IL2CPP_objectString = "string boxed as cpp object";
|
||||
public static Il2CppSystem.String IL2CPP_il2cppString = "string boxed as cpp string";
|
||||
public static string nullString = null;
|
||||
|
||||
public static Il2CppSystem.Object RandomBoxedColor
|
||||
private static void Init_IL2CPP()
|
||||
{
|
||||
get
|
||||
{
|
||||
int ran = UnityEngine.Random.Range(0, 3);
|
||||
switch (ran)
|
||||
{
|
||||
case 1: return new Color32().BoxIl2CppObject();
|
||||
case 2: return Color.magenta.BoxIl2CppObject();
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static Il2CppSystem.Collections.Hashtable cppHashset;
|
||||
|
||||
public static Dictionary<Il2CppSystem.String, Il2CppSystem.Object> CppBoxedDict;
|
||||
|
||||
#endif
|
||||
|
||||
static TestClass()
|
||||
{
|
||||
for (int i = 0; i < BigList.Capacity; i++)
|
||||
BigList.Add(i.ToString());
|
||||
|
||||
#if CPP
|
||||
ExplorerCore.Log($"IL2CPP 1: Il2Cpp Dictionary<string, string>");
|
||||
IL2CPP_Dict = new Il2CppSystem.Collections.Generic.Dictionary<string, string>();
|
||||
IL2CPP_Dict.Add("key1", "value1");
|
||||
IL2CPP_Dict.Add("key2", "value2");
|
||||
IL2CPP_Dict.Add("key3", "value3");
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 2: Il2Cpp Hashtable");
|
||||
IL2CPP_HashTable = new Il2CppSystem.Collections.Hashtable();
|
||||
IL2CPP_HashTable.Add("key1", "value1");
|
||||
IL2CPP_HashTable.Add("key2", "value2");
|
||||
IL2CPP_HashTable.Add("key3", "value3");
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 3: Il2Cpp IDictionary");
|
||||
var dict2 = new Il2CppSystem.Collections.Generic.Dictionary<string, string>();
|
||||
dict2.Add("key1", "value1");
|
||||
IL2CPP_IDict = dict2.TryCast<Il2CppSystem.Collections.IDictionary>();
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 4: Il2Cpp List of Il2Cpp Object");
|
||||
var list = new Il2CppSystem.Collections.Generic.List<Il2CppSystem.Object>(5);
|
||||
list.Add("one");
|
||||
list.Add("two");
|
||||
IL2CPP_IList = list.TryCast<Il2CppSystem.Collections.IList>();
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 5: Il2Cpp List of strings");
|
||||
IL2CPP_ListString = new Il2CppSystem.Collections.Generic.List<string>();
|
||||
IL2CPP_ListString.Add("hello,");
|
||||
IL2CPP_ListString.Add("world!");
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 6: Il2Cpp HashSet of strings");
|
||||
IL2CPP_HashSet = new Il2CppSystem.Collections.Generic.HashSet<string>();
|
||||
IL2CPP_HashSet.Add("one");
|
||||
IL2CPP_HashSet.Add("two");
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 7: Dictionary of Il2Cpp String and Il2Cpp Object");
|
||||
CppBoxedDict = new Dictionary<Il2CppSystem.String, Il2CppSystem.Object>();
|
||||
CppBoxedDict.Add("1", new Il2CppSystem.Int32 { m_value = 1 }.BoxIl2CppObject());
|
||||
CppBoxedDict.Add("2", new Il2CppSystem.Int32 { m_value = 2 }.BoxIl2CppObject());
|
||||
CppBoxedDict.Add("3", new Il2CppSystem.Int32 { m_value = 3 }.BoxIl2CppObject());
|
||||
CppBoxedDict.Add("4", new Il2CppSystem.Int32 { m_value = 4 }.BoxIl2CppObject());
|
||||
|
||||
cppDecimal = new Il2CppSystem.Decimal(1f);
|
||||
cppDecimalBoxed = new Il2CppSystem.Decimal(1f).BoxIl2CppObject();
|
||||
cppVector3Boxed = Vector3.down.BoxIl2CppObject();
|
||||
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 8: List of boxed Il2Cpp Objects");
|
||||
IL2CPP_listOfBoxedObjects = new List<Il2CppSystem.Object>();
|
||||
IL2CPP_listOfBoxedObjects.Add((Il2CppSystem.String)"boxedString");
|
||||
IL2CPP_listOfBoxedObjects.Add(new Il2CppSystem.Int32 { m_value = 5 }.BoxIl2CppObject());
|
||||
IL2CPP_listOfBoxedObjects.Add(Color.red.BoxIl2CppObject());
|
||||
|
||||
// boxed enum test
|
||||
try
|
||||
{
|
||||
var cppType = Il2CppType.Of<CameraClearFlags>();
|
||||
@ -251,9 +208,10 @@ namespace UnityExplorer.Tests
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Test fail: {ex}");
|
||||
ExplorerCore.LogWarning($"Boxed enum test fail: {ex}");
|
||||
}
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 9: Il2Cpp struct array of ints");
|
||||
IL2CPP_structArray = new UnhollowerBaseLib.Il2CppStructArray<int>(5);
|
||||
IL2CPP_structArray[0] = 0;
|
||||
IL2CPP_structArray[1] = 1;
|
||||
@ -261,24 +219,21 @@ namespace UnityExplorer.Tests
|
||||
IL2CPP_structArray[3] = 3;
|
||||
IL2CPP_structArray[4] = 4;
|
||||
|
||||
IL2CPP_stringArray = new UnhollowerBaseLib.Il2CppStringArray(2);
|
||||
IL2CPP_stringArray[0] = "hello, ";
|
||||
IL2CPP_stringArray[1] = "world!";
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 10: Il2Cpp reference array of boxed objects");
|
||||
IL2CPP_ReferenceArray = new UnhollowerBaseLib.Il2CppReferenceArray<Il2CppSystem.Object>(3);
|
||||
IL2CPP_ReferenceArray[0] = new Il2CppSystem.Int32 { m_value = 5 }.BoxIl2CppObject();
|
||||
IL2CPP_ReferenceArray[1] = null;
|
||||
IL2CPP_ReferenceArray[2] = (Il2CppSystem.String)"whats up";
|
||||
|
||||
ExplorerCore.Log($"IL2CPP 11: Misc il2cpp members");
|
||||
cppBoxedInt = new Il2CppSystem.Int32() { m_value = 5 }.BoxIl2CppObject();
|
||||
cppInt = new Il2CppSystem.Int32 { m_value = 420 };
|
||||
cppDecimal = new Il2CppSystem.Decimal(1f);
|
||||
cppDecimalBoxed = new Il2CppSystem.Decimal(1f).BoxIl2CppObject();
|
||||
cppVector3Boxed = Vector3.down.BoxIl2CppObject();
|
||||
|
||||
cppHashset = new Il2CppSystem.Collections.Hashtable();
|
||||
cppHashset.Add("key1", "itemOne");
|
||||
cppHashset.Add("key2", "itemTwo");
|
||||
cppHashset.Add("key3", "itemThree");
|
||||
|
||||
#endif
|
||||
ExplorerCore.Log($"Finished Init_Il2Cpp");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ namespace UnityExplorer
|
||||
/// </summary>
|
||||
public static bool ContainsIgnoreCase(this string _this, string s)
|
||||
{
|
||||
return ParseUtility.en_US.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0;
|
||||
return CultureInfo.CurrentCulture.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
@ -10,8 +10,6 @@ namespace UnityExplorer
|
||||
{
|
||||
public static class ParseUtility
|
||||
{
|
||||
public static CultureInfo en_US = new CultureInfo("en-US");
|
||||
|
||||
private static readonly HashSet<Type> nonPrimitiveTypes = new HashSet<Type>
|
||||
{
|
||||
typeof(string),
|
||||
@ -19,20 +17,18 @@ namespace UnityExplorer
|
||||
typeof(DateTime),
|
||||
};
|
||||
|
||||
public const string NUMBER_FORMAT = "0.####";
|
||||
// Helper for formatting float/double/decimal numbers to maximum of 4 decimal points.
|
||||
// And also for formatting a sequence of those numbers, ie a Vector3, Color etc
|
||||
|
||||
public static readonly string NumberFormatString = $"0.####";
|
||||
private static readonly Dictionary<int, string> numSequenceStrings = new Dictionary<int, string>();
|
||||
|
||||
// Helper for formatting float/double/decimal numbers to maximum of 4 decimal points.
|
||||
public static string FormatDecimalSequence(params object[] numbers)
|
||||
{
|
||||
if (numbers.Length <= 0)
|
||||
return null;
|
||||
|
||||
int count = numbers.Length;
|
||||
var formatString = GetSequenceFormatString(count);
|
||||
|
||||
return string.Format(en_US, formatString, numbers);
|
||||
return string.Format(CultureInfo.CurrentCulture, GetSequenceFormatString(numbers.Length), numbers);
|
||||
}
|
||||
|
||||
public static string GetSequenceFormatString(int count)
|
||||
@ -46,19 +42,19 @@ namespace UnityExplorer
|
||||
string[] strings = new string[count];
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
strings[i] = $"{{{i}:{NUMBER_FORMAT}}}";
|
||||
strings[i] = $"{{{i}:{NumberFormatString}}}";
|
||||
|
||||
string s = string.Join(", ", strings);
|
||||
|
||||
numSequenceStrings.Add(count, s);
|
||||
return s;
|
||||
string ret = string.Join(" ", strings);
|
||||
numSequenceStrings.Add(count, ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Main parsing API
|
||||
|
||||
public static bool CanParse(Type type)
|
||||
{
|
||||
if (string.IsNullOrEmpty(type.FullName))
|
||||
return false;
|
||||
return type.IsPrimitive || type.IsEnum || nonPrimitiveTypes.Contains(type) || customTypes.ContainsKey(type.FullName);
|
||||
return !string.IsNullOrEmpty(type?.FullName)
|
||||
&& (type.IsPrimitive || type.IsEnum || nonPrimitiveTypes.Contains(type) || customTypes.ContainsKey(type.FullName));
|
||||
}
|
||||
|
||||
public static bool TryParse(string input, Type type, out object obj, out Exception parseException)
|
||||
@ -143,7 +139,7 @@ namespace UnityExplorer
|
||||
else if (formattedTypes.Contains(type))
|
||||
{
|
||||
return ReflectionUtility.GetMethodInfo(type, "ToString", new Type[] { typeof(string), typeof(IFormatProvider) })
|
||||
.Invoke(obj, new object[] { NUMBER_FORMAT, en_US })
|
||||
.Invoke(obj, new object[] { NumberFormatString, CultureInfo.CurrentCulture })
|
||||
as string;
|
||||
}
|
||||
else
|
||||
@ -166,9 +162,7 @@ namespace UnityExplorer
|
||||
try
|
||||
{
|
||||
if (type.IsEnum)
|
||||
{
|
||||
typeInputExamples.Add(type.AssemblyQualifiedName, Enum.GetNames(type).First());
|
||||
}
|
||||
else
|
||||
{
|
||||
var instance = Activator.CreateInstance(type);
|
||||
@ -222,10 +216,10 @@ namespace UnityExplorer
|
||||
{
|
||||
Vector2 vector = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
vector.x = float.Parse(split[0].Trim(), en_US);
|
||||
vector.y = float.Parse(split[1].Trim(), en_US);
|
||||
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
|
||||
return vector;
|
||||
}
|
||||
@ -244,11 +238,11 @@ namespace UnityExplorer
|
||||
{
|
||||
Vector3 vector = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
vector.x = float.Parse(split[0].Trim(), en_US);
|
||||
vector.y = float.Parse(split[1].Trim(), en_US);
|
||||
vector.z = float.Parse(split[2].Trim(), en_US);
|
||||
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
|
||||
return vector;
|
||||
}
|
||||
@ -267,12 +261,12 @@ namespace UnityExplorer
|
||||
{
|
||||
Vector4 vector = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
vector.x = float.Parse(split[0].Trim(), en_US);
|
||||
vector.y = float.Parse(split[1].Trim(), en_US);
|
||||
vector.z = float.Parse(split[2].Trim(), en_US);
|
||||
vector.w = float.Parse(split[3].Trim(), en_US);
|
||||
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.w = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
|
||||
|
||||
return vector;
|
||||
}
|
||||
@ -291,22 +285,22 @@ namespace UnityExplorer
|
||||
{
|
||||
Vector3 vector = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
if (split.Length == 4)
|
||||
{
|
||||
Quaternion quat = default;
|
||||
quat.x = float.Parse(split[0].Trim(), en_US);
|
||||
quat.y = float.Parse(split[1].Trim(), en_US);
|
||||
quat.z = float.Parse(split[2].Trim(), en_US);
|
||||
quat.w = float.Parse(split[3].Trim(), en_US);
|
||||
quat.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
quat.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
quat.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
quat.w = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
|
||||
return quat;
|
||||
}
|
||||
else
|
||||
{
|
||||
vector.x = float.Parse(split[0].Trim(), en_US);
|
||||
vector.y = float.Parse(split[1].Trim(), en_US);
|
||||
vector.z = float.Parse(split[2].Trim(), en_US);
|
||||
vector.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
vector.z = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
return Quaternion.Euler(vector);
|
||||
}
|
||||
}
|
||||
@ -327,12 +321,12 @@ namespace UnityExplorer
|
||||
{
|
||||
Rect rect = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
rect.x = float.Parse(split[0].Trim(), en_US);
|
||||
rect.y = float.Parse(split[1].Trim(), en_US);
|
||||
rect.width = float.Parse(split[2].Trim(), en_US);
|
||||
rect.height = float.Parse(split[3].Trim(), en_US);
|
||||
rect.x = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
rect.y = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
rect.width = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
rect.height = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
|
||||
|
||||
return rect;
|
||||
}
|
||||
@ -351,13 +345,13 @@ namespace UnityExplorer
|
||||
{
|
||||
Color color = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
color.r = float.Parse(split[0].Trim(), en_US);
|
||||
color.g = float.Parse(split[1].Trim(), en_US);
|
||||
color.b = float.Parse(split[2].Trim(), en_US);
|
||||
color.r = float.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
color.g = float.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
color.b = float.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
if (split.Length > 3)
|
||||
color.a = float.Parse(split[3].Trim(), en_US);
|
||||
color.a = float.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
|
||||
else
|
||||
color.a = 1;
|
||||
|
||||
@ -378,13 +372,13 @@ namespace UnityExplorer
|
||||
{
|
||||
Color32 color = default;
|
||||
|
||||
var split = input.Split(',');
|
||||
var split = input.Split(' ');
|
||||
|
||||
color.r = byte.Parse(split[0].Trim(), en_US);
|
||||
color.g = byte.Parse(split[1].Trim(), en_US);
|
||||
color.b = byte.Parse(split[2].Trim(), en_US);
|
||||
color.r = byte.Parse(split[0].Trim(), CultureInfo.CurrentCulture);
|
||||
color.g = byte.Parse(split[1].Trim(), CultureInfo.CurrentCulture);
|
||||
color.b = byte.Parse(split[2].Trim(), CultureInfo.CurrentCulture);
|
||||
if (split.Length > 3)
|
||||
color.a = byte.Parse(split[3].Trim(), en_US);
|
||||
color.a = byte.Parse(split[3].Trim(), CultureInfo.CurrentCulture);
|
||||
else
|
||||
color.a = 255;
|
||||
|
||||
@ -397,7 +391,7 @@ namespace UnityExplorer
|
||||
return null;
|
||||
|
||||
// ints, this is fine
|
||||
return $"{color.r}, {color.g}, {color.b}, {color.a}";
|
||||
return $"{color.r} {color.g} {color.b} {color.a}";
|
||||
}
|
||||
|
||||
// Layermask (Int32)
|
||||
|
@ -13,7 +13,6 @@ namespace UnityExplorer
|
||||
public static class ToStringUtility
|
||||
{
|
||||
internal static Dictionary<string, MethodInfo> toStringMethods = new Dictionary<string, MethodInfo>();
|
||||
internal static Dictionary<string, MethodInfo> toStringFormattedMethods = new Dictionary<string, MethodInfo>();
|
||||
|
||||
private const string nullString = "<color=grey>null</color>";
|
||||
private const string nullUnknown = nullString + " (?)";
|
||||
@ -132,22 +131,12 @@ namespace UnityExplorer
|
||||
|
||||
var type = value.GetActualType();
|
||||
|
||||
// Find and cache the relevant ToString method for this Type, if haven't already.
|
||||
// Find and cache the ToString method for this Type, if haven't already.
|
||||
|
||||
if (!toStringMethods.ContainsKey(type.AssemblyQualifiedName))
|
||||
{
|
||||
try
|
||||
{
|
||||
var formatMethod = type.GetMethod("ToString", ArgumentUtility.ParseArgs);
|
||||
formatMethod.Invoke(value, new object[] { ParseUtility.NUMBER_FORMAT });
|
||||
toStringFormattedMethods.Add(type.AssemblyQualifiedName, formatMethod);
|
||||
toStringMethods.Add(type.AssemblyQualifiedName, null);
|
||||
}
|
||||
catch
|
||||
{
|
||||
var toStringMethod = type.GetMethod("ToString", ArgumentUtility.EmptyTypes);
|
||||
toStringMethods.Add(type.AssemblyQualifiedName, toStringMethod);
|
||||
}
|
||||
var toStringMethod = type.GetMethod("ToString", ArgumentUtility.EmptyTypes);
|
||||
toStringMethods.Add(type.AssemblyQualifiedName, toStringMethod);
|
||||
}
|
||||
|
||||
// Invoke the ToString method on the object
|
||||
@ -157,10 +146,7 @@ namespace UnityExplorer
|
||||
string toString;
|
||||
try
|
||||
{
|
||||
if (toStringFormattedMethods.TryGetValue(type.AssemblyQualifiedName, out MethodInfo formatMethod))
|
||||
toString = (string)formatMethod.Invoke(value, new object[] { ParseUtility.NUMBER_FORMAT });
|
||||
else
|
||||
toString = (string)toStringMethods[type.AssemblyQualifiedName].Invoke(value, ArgumentUtility.EmptyArgs);
|
||||
toString = (string)toStringMethods[type.AssemblyQualifiedName].Invoke(value, ArgumentUtility.EmptyArgs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
@ -2,9 +2,11 @@
|
||||
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
|
||||
@ -107,5 +109,16 @@ namespace UnityExplorer
|
||||
|
||||
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>>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -11,7 +11,8 @@ using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.Tests;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UnityExplorer.ObjectExplorer;
|
||||
using UnityExplorer.UI.Panels;
|
||||
|
||||
namespace UnityExplorer
|
||||
@ -19,13 +20,15 @@ namespace UnityExplorer
|
||||
public static class ExplorerCore
|
||||
{
|
||||
public const string NAME = "UnityExplorer";
|
||||
public const string VERSION = "4.0.0";
|
||||
public const string VERSION = "4.3.0";
|
||||
public const string AUTHOR = "Sinai";
|
||||
public const string GUID = "com.sinai.unityexplorer";
|
||||
|
||||
public static IExplorerLoader Loader { get; private set; }
|
||||
public static RuntimeContext Context { get; internal set; }
|
||||
|
||||
public static HarmonyLib.Harmony Harmony { get; } = new HarmonyLib.Harmony(GUID);
|
||||
|
||||
/// <summary>
|
||||
/// Initialize UnityExplorer with the provided Loader implementation.
|
||||
/// </summary>
|
||||
@ -46,10 +49,9 @@ namespace UnityExplorer
|
||||
Directory.CreateDirectory(Loader.ExplorerFolder);
|
||||
|
||||
ConfigManager.Init(Loader.ConfigHandler);
|
||||
|
||||
ReflectionUtility.Init();
|
||||
|
||||
RuntimeProvider.Init();
|
||||
|
||||
SceneHandler.Init();
|
||||
InputManager.Init();
|
||||
|
||||
@ -64,14 +66,13 @@ namespace UnityExplorer
|
||||
private static IEnumerator SetupCoroutine()
|
||||
{
|
||||
yield return null;
|
||||
|
||||
float start = Time.realtimeSinceStartup;
|
||||
float prevRealTime = Time.realtimeSinceStartup;
|
||||
float delay = ConfigManager.Startup_Delay_Time.Value;
|
||||
|
||||
while (delay > 0)
|
||||
{
|
||||
float diff = Math.Max(Time.deltaTime, Time.realtimeSinceStartup - start);
|
||||
float diff = Math.Max(Time.deltaTime, Time.realtimeSinceStartup - prevRealTime);
|
||||
delay -= diff;
|
||||
prevRealTime = Time.realtimeSinceStartup;
|
||||
yield return null;
|
||||
}
|
||||
|
||||
@ -102,7 +103,7 @@ namespace UnityExplorer
|
||||
RuntimeProvider.Instance.ProcessOnPostRender();
|
||||
}
|
||||
|
||||
#region LOGGING
|
||||
#region LOGGING
|
||||
|
||||
public static void Log(object message)
|
||||
=> Log(message, LogType.Log);
|
||||
@ -145,6 +146,6 @@ namespace UnityExplorer
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
63
src/Hooks/AddHookCell.cs
Normal file
63
src/Hooks/AddHookCell.cs
Normal file
@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
79
src/Hooks/HookCell.cs
Normal file
79
src/Hooks/HookCell.cs
Normal file
@ -0,0 +1,79 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
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", "Log 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);
|
||||
}
|
||||
}
|
||||
}
|
193
src/Hooks/HookInstance.cs
Normal file
193
src/Hooks/HookInstance.cs
Normal file
@ -0,0 +1,193 @@
|
||||
using System;
|
||||
using System.CodeDom.Compiler;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using HarmonyLib;
|
||||
using Microsoft.CSharp;
|
||||
using UnityExplorer.CSConsole;
|
||||
|
||||
namespace UnityExplorer.Hooks
|
||||
{
|
||||
public class HookInstance
|
||||
{
|
||||
private static readonly StringBuilder evalOutput = new StringBuilder();
|
||||
private static readonly ScriptEvaluator scriptEvaluator = new ScriptEvaluator(new StringWriter(evalOutput));
|
||||
|
||||
// Instance
|
||||
|
||||
public bool Enabled;
|
||||
public MethodInfo TargetMethod;
|
||||
public string GeneratedSource;
|
||||
|
||||
private string shortSignature;
|
||||
private PatchProcessor patchProcessor;
|
||||
private HarmonyMethod patchDelegate;
|
||||
private MethodInfo patchDelegateMethodInfo;
|
||||
|
||||
public HookInstance(MethodInfo targetMethod)
|
||||
{
|
||||
this.TargetMethod = targetMethod;
|
||||
this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
|
||||
GenerateProcessorAndDelegate();
|
||||
Patch();
|
||||
}
|
||||
|
||||
private void GenerateProcessorAndDelegate()
|
||||
{
|
||||
try
|
||||
{
|
||||
patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod);
|
||||
|
||||
// Dynamically compile the patch method
|
||||
|
||||
scriptEvaluator.Run(GeneratePatchSourceCode(TargetMethod));
|
||||
|
||||
// Get the compiled method and check for errors
|
||||
|
||||
string output = scriptEvaluator._textWriter.ToString();
|
||||
var outputSplit = output.Split('\n');
|
||||
if (outputSplit.Length >= 2)
|
||||
output = outputSplit[outputSplit.Length - 2];
|
||||
evalOutput.Clear();
|
||||
if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
|
||||
throw new FormatException($"Unable to compile the code. Evaluator's last output was:\r\n{output}");
|
||||
|
||||
// Could publicize MCS to avoid this reflection, but not bothering for now
|
||||
var source = (Mono.CSharp.CompilationSourceFile)ReflectionUtility.GetFieldInfo(typeof(Mono.CSharp.Evaluator), "source_file")
|
||||
.GetValue(scriptEvaluator);
|
||||
var type = (Mono.CSharp.Class)source.Containers.Last();
|
||||
var systemType = ((Mono.CSharp.TypeSpec)ReflectionUtility.GetPropertyInfo(typeof(Mono.CSharp.TypeDefinition), "Definition")
|
||||
.GetValue(type, null))
|
||||
.GetMetaInfo();
|
||||
|
||||
this.patchDelegateMethodInfo = systemType.GetMethod("Patch", ReflectionUtility.FLAGS);
|
||||
|
||||
// Actually create the harmony patch
|
||||
this.patchDelegate = new HarmonyMethod(patchDelegateMethodInfo);
|
||||
patchProcessor.AddPostfix(patchDelegate);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception creating patch processor for target method {TargetMethod.FullDescription()}!\r\n{ex}");
|
||||
}
|
||||
}
|
||||
|
||||
private string GeneratePatchSourceCode(MethodInfo targetMethod)
|
||||
{
|
||||
var codeBuilder = new StringBuilder();
|
||||
|
||||
codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}");
|
||||
codeBuilder.AppendLine("{");
|
||||
|
||||
// Arguments
|
||||
|
||||
codeBuilder.Append(" public static void Patch(System.Reflection.MethodBase __originalMethod");
|
||||
|
||||
if (!targetMethod.IsStatic)
|
||||
codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance");
|
||||
|
||||
if (targetMethod.ReturnType != typeof(void))
|
||||
codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result");
|
||||
|
||||
int paramIdx = 0;
|
||||
var parameters = targetMethod.GetParameters();
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
codeBuilder.Append($", {param.ParameterType.FullName} __{paramIdx}");
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
codeBuilder.Append(")\n");
|
||||
|
||||
// Patch body
|
||||
|
||||
codeBuilder.AppendLine(" {");
|
||||
|
||||
codeBuilder.AppendLine(" try {");
|
||||
|
||||
// Log message
|
||||
|
||||
var logMessage = new StringBuilder();
|
||||
logMessage.AppendLine($"$@\"Patch called: {shortSignature}");
|
||||
|
||||
if (!targetMethod.IsStatic)
|
||||
logMessage.AppendLine("__instance: {__instance.ToString()}");
|
||||
|
||||
paramIdx = 0;
|
||||
foreach (var param in parameters)
|
||||
{
|
||||
if (param.ParameterType.IsValueType)
|
||||
logMessage.AppendLine($"Parameter {paramIdx}: {{__{paramIdx}.ToString()}}");
|
||||
else
|
||||
logMessage.AppendLine($"Parameter {paramIdx}: {{__{paramIdx}?.ToString() ?? \"null\"}}");
|
||||
paramIdx++;
|
||||
}
|
||||
|
||||
if (targetMethod.ReturnType != typeof(void))
|
||||
{
|
||||
if (targetMethod.ReturnType.IsValueType)
|
||||
logMessage.AppendLine("Return value: {__result.ToString()}");
|
||||
else
|
||||
logMessage.AppendLine("Return value: {__result?.ToString() ?? \"null\"}");
|
||||
}
|
||||
|
||||
logMessage.Append('"');
|
||||
|
||||
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(" }");
|
||||
|
||||
// End class
|
||||
|
||||
codeBuilder.AppendLine("}");
|
||||
|
||||
return GeneratedSource = codeBuilder.ToString();
|
||||
}
|
||||
|
||||
public void TogglePatch()
|
||||
{
|
||||
Enabled = !Enabled;
|
||||
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()
|
||||
{
|
||||
if (!Enabled)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
this.patchProcessor.Unpatch(patchDelegateMethodInfo);
|
||||
Enabled = false;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.LogWarning($"Exception unpatching method: {ex}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
233
src/Hooks/HookManager.cs
Normal file
233
src/Hooks/HookManager.cs
Normal file
@ -0,0 +1,233 @@
|
||||
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.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
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);
|
||||
|
||||
public int ItemCount => addingMethods ? currentFilteredMethods.Count : currentHooks.Count;
|
||||
|
||||
private bool addingMethods;
|
||||
private HashSet<string> hookedSignatures = new HashSet<string>();
|
||||
private readonly OrderedDictionary currentHooks = new OrderedDictionary();
|
||||
private readonly List<MethodInfo> currentAddEligableMethods = new List<MethodInfo>();
|
||||
private readonly List<MethodInfo> currentFilteredMethods = new List<MethodInfo>();
|
||||
|
||||
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();
|
||||
currentFilteredMethods.Clear();
|
||||
currentAddEligableMethods.Clear();
|
||||
foreach (var method in type.GetMethods(ReflectionUtility.FLAGS))
|
||||
{
|
||||
if (method.IsGenericMethod || method.IsAbstract || ReflectionUtility.IsBlacklisted(method))
|
||||
continue;
|
||||
currentAddEligableMethods.Add(method);
|
||||
currentFilteredMethods.Add(method);
|
||||
}
|
||||
|
||||
addingMethods = true;
|
||||
Panel.SetAddPanelActive(true);
|
||||
Panel.MethodResultsScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
public void CloseAddHooks()
|
||||
{
|
||||
addingMethods = false;
|
||||
Panel.SetAddPanelActive(false);
|
||||
Panel.HooksScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void OnHookAllClicked()
|
||||
{
|
||||
foreach (var method in currentAddEligableMethods)
|
||||
AddHook(method);
|
||||
Panel.MethodResultsScrollPool.Refresh(true, false);
|
||||
}
|
||||
|
||||
public void AddHookClicked(int index)
|
||||
{
|
||||
if (index >= this.currentFilteredMethods.Count)
|
||||
return;
|
||||
|
||||
AddHook(currentFilteredMethods[index]);
|
||||
Panel.MethodResultsScrollPool.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 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)
|
||||
{
|
||||
var hook = (HookInstance)currentHooks[index];
|
||||
ExplorerCore.Log(hook.GeneratedSource);
|
||||
}
|
||||
|
||||
public void OnAddHookFilterInputChanged(string input)
|
||||
{
|
||||
currentFilteredMethods.Clear();
|
||||
|
||||
if (string.IsNullOrEmpty(input))
|
||||
currentFilteredMethods.AddRange(currentAddEligableMethods);
|
||||
else
|
||||
{
|
||||
foreach (var method in currentAddEligableMethods)
|
||||
{
|
||||
if (method.Name.ContainsIgnoreCase(input))
|
||||
currentFilteredMethods.Add(method);
|
||||
}
|
||||
}
|
||||
|
||||
Panel.MethodResultsScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
// OnBorrow methods not needed
|
||||
public void OnCellBorrowed(HookCell cell) { }
|
||||
public void OnCellBorrowed(AddHookCell cell) { }
|
||||
|
||||
// Set current hook 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 = HighlightMethod(hook.TargetMethod);
|
||||
|
||||
cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "Enabled" : "Disabled";
|
||||
RuntimeProvider.Instance.SetColorBlockAuto(cell.ToggleActiveButton.Component,
|
||||
hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f));
|
||||
}
|
||||
|
||||
// Set eligable method cell
|
||||
|
||||
public void SetCell(AddHookCell cell, int index)
|
||||
{
|
||||
if (index >= this.currentFilteredMethods.Count)
|
||||
{
|
||||
cell.Disable();
|
||||
return;
|
||||
}
|
||||
|
||||
cell.CurrentDisplayedIndex = index;
|
||||
var method = this.currentFilteredMethods[index];
|
||||
|
||||
cell.MethodNameLabel.text = 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);
|
||||
}
|
||||
}
|
||||
|
||||
// private static readonly string VOID_HIGHLIGHT = $"<color=#{SignatureHighlighter.keywordBlueHex}>void</color> ";
|
||||
|
||||
private static readonly Dictionary<string, string> highlightedMethods = new Dictionary<string, string>();
|
||||
|
||||
private string HighlightMethod(MethodInfo method)
|
||||
{
|
||||
var sig = method.FullDescription();
|
||||
if (highlightedMethods.ContainsKey(sig))
|
||||
return highlightedMethods[sig];
|
||||
|
||||
var sb = new StringBuilder();
|
||||
|
||||
// declaring type
|
||||
sb.Append(SignatureHighlighter.Parse(method.DeclaringType, false));
|
||||
sb.Append('.');
|
||||
|
||||
// method name
|
||||
var color = !method.IsStatic
|
||||
? SignatureHighlighter.METHOD_INSTANCE
|
||||
: SignatureHighlighter.METHOD_STATIC;
|
||||
sb.Append($"<color={color}>{method.Name}</color>");
|
||||
|
||||
// arguments
|
||||
sb.Append('(');
|
||||
var args = method.GetParameters();
|
||||
if (args != null && args.Any())
|
||||
{
|
||||
int i = 0;
|
||||
foreach (var param in args)
|
||||
{
|
||||
sb.Append(SignatureHighlighter.Parse(param.ParameterType, false));
|
||||
sb.Append(' ');
|
||||
sb.Append($"<color={SignatureHighlighter.LOCAL_ARG}>{param.Name}</color>");
|
||||
i++;
|
||||
if (i < args.Length)
|
||||
sb.Append(", ");
|
||||
}
|
||||
}
|
||||
sb.Append(')');
|
||||
|
||||
var ret = sb.ToString();
|
||||
highlightedMethods.Add(sig, ret);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
@ -2,19 +2,22 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
|
||||
<Target Name="ILRepacker" AfterTargets="Build">
|
||||
<!-- Actual merged assemblies -->
|
||||
<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" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Required references for ILRepack -->
|
||||
<ItemGroup>
|
||||
<ReferenceFolders Include="..\lib\" />
|
||||
<ReferenceFolders Include="..\lib\HarmonyX\Harmony\bin\Release\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\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ILRepack
|
||||
Parallel="true"
|
||||
Internalize="true"
|
||||
@ -22,7 +25,8 @@
|
||||
LibraryPath="@(ReferenceFolders)"
|
||||
InputAssemblies="@(InputAssemblies)"
|
||||
TargetKind="Dll"
|
||||
OutputFile="$(OutputPath)$(AssemblyName).dll" />
|
||||
OutputFile="$(OutputPath)$(AssemblyName).dll"
|
||||
/>
|
||||
</Target>
|
||||
|
||||
</Project>
|
@ -6,13 +6,13 @@ using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.UI.ObjectPool;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Utility;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI.Widgets.AutoComplete;
|
||||
|
||||
namespace UnityExplorer.UI.Inspectors
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class GameObjectInspector : InspectorBase
|
||||
{
|
||||
@ -64,7 +64,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
addCompInput.Text = "";
|
||||
|
||||
TransformTree.Clear();
|
||||
ComponentList.Clear();
|
||||
UpdateComponents();
|
||||
}
|
||||
|
||||
public override void CloseInspector()
|
||||
@ -113,6 +113,9 @@ namespace UnityExplorer.UI.Inspectors
|
||||
|
||||
private IEnumerable<GameObject> GetTransformEntries()
|
||||
{
|
||||
if (!GOTarget)
|
||||
return Enumerable.Empty<GameObject>();
|
||||
|
||||
cachedChildren.Clear();
|
||||
for (int i = 0; i < GOTarget.transform.childCount; i++)
|
||||
cachedChildren.Add(GOTarget.transform.GetChild(i).gameObject);
|
||||
@ -125,41 +128,59 @@ namespace UnityExplorer.UI.Inspectors
|
||||
private readonly List<bool> behaviourEnabledStates = new List<bool>();
|
||||
|
||||
// ComponentList.GetRootEntriesMethod
|
||||
private List<Component> GetComponentEntries() => componentEntries;
|
||||
private List<Component> GetComponentEntries() => GOTarget ? componentEntries : Enumerable.Empty<Component>().ToList();
|
||||
|
||||
public void UpdateComponents()
|
||||
{
|
||||
if (!GOTarget)
|
||||
{
|
||||
componentEntries.Clear();
|
||||
compInstanceIDs.Clear();
|
||||
behaviourEntries.Clear();
|
||||
behaviourEnabledStates.Clear();
|
||||
ComponentList.RefreshData();
|
||||
ComponentList.ScrollPool.Refresh(true, true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we actually need to refresh the component cells or not.
|
||||
var comps = GOTarget.GetComponents<Component>();
|
||||
var behaviours = GOTarget.GetComponents<Behaviour>();
|
||||
|
||||
bool needRefresh = false;
|
||||
if (comps.Length != componentEntries.Count || behaviours.Length != behaviourEntries.Count)
|
||||
{
|
||||
needRefresh = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var comp in comps)
|
||||
{
|
||||
if (!compInstanceIDs.Contains(comp.GetInstanceID()))
|
||||
{
|
||||
needRefresh = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!needRefresh)
|
||||
int count = 0;
|
||||
foreach (var comp in comps)
|
||||
{
|
||||
if (!comp)
|
||||
continue;
|
||||
count++;
|
||||
if (!compInstanceIDs.Contains(comp.GetInstanceID()))
|
||||
{
|
||||
for (int i = 0; i < behaviours.Length; i++)
|
||||
needRefresh = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!needRefresh)
|
||||
{
|
||||
if (count != componentEntries.Count)
|
||||
needRefresh = true;
|
||||
else
|
||||
{
|
||||
count = 0;
|
||||
foreach (var behaviour in behaviours)
|
||||
{
|
||||
var behaviour = behaviours[i];
|
||||
if (behaviour.enabled != behaviourEnabledStates[i])
|
||||
if (!behaviour)
|
||||
continue;
|
||||
if (count >= behaviourEnabledStates.Count || behaviour.enabled != behaviourEnabledStates[count])
|
||||
{
|
||||
needRefresh = true;
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
if (!needRefresh && count != behaviourEntries.Count)
|
||||
needRefresh = true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -168,9 +189,9 @@ namespace UnityExplorer.UI.Inspectors
|
||||
|
||||
componentEntries.Clear();
|
||||
compInstanceIDs.Clear();
|
||||
|
||||
foreach (var comp in comps)
|
||||
{
|
||||
if (!comp) continue;
|
||||
componentEntries.Add(comp);
|
||||
compInstanceIDs.Add(comp.GetInstanceID());
|
||||
}
|
||||
@ -179,6 +200,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
behaviourEnabledStates.Clear();
|
||||
foreach (var behaviour in behaviours)
|
||||
{
|
||||
if (!behaviour) continue;
|
||||
behaviourEntries.Add(behaviour);
|
||||
behaviourEnabledStates.Add(behaviour.enabled);
|
||||
}
|
||||
@ -198,7 +220,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
|
||||
private void OnAddComponentClicked(string input)
|
||||
{
|
||||
if (ReflectionUtility.AllTypes.TryGetValue(input, out Type type))
|
||||
if (ReflectionUtility.GetTypeByName(input) is Type type)
|
||||
{
|
||||
try
|
||||
{
|
||||
@ -221,12 +243,12 @@ namespace UnityExplorer.UI.Inspectors
|
||||
|
||||
public override GameObject CreateContent(GameObject parent)
|
||||
{
|
||||
UIRoot = UIFactory.CreateVerticalGroup(Pool<GameObjectInspector>.Instance.InactiveHolder,
|
||||
"GameObjectInspector", true, false, true, true, 5, new Vector4(4, 4, 4, 4), new Color(0.065f, 0.065f, 0.065f));
|
||||
UIRoot = UIFactory.CreateVerticalGroup(parent, "GameObjectInspector", true, false, true, true, 5,
|
||||
new Vector4(4, 4, 4, 4), new Color(0.065f, 0.065f, 0.065f));
|
||||
|
||||
var scrollObj = UIFactory.CreateScrollView(UIRoot, "GameObjectInspector", out Content, out var scrollbar,
|
||||
new Color(0.065f, 0.065f, 0.065f));
|
||||
UIFactory.SetLayoutElement(scrollObj, minHeight: 300, flexibleWidth: 9999, flexibleHeight: 1);
|
||||
UIFactory.SetLayoutElement(scrollObj, minHeight: 250, preferredHeight: 300, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(Content, spacing: 3, padTop: 2, padBottom: 2, padLeft: 2, padRight: 2);
|
||||
|
||||
@ -244,10 +266,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
{
|
||||
var listHolder = UIFactory.CreateUIObject("ListHolders", UIRoot);
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(listHolder, false, true, true, true, 8, 2, 2, 2, 2);
|
||||
UIFactory.SetLayoutElement(listHolder, minHeight: 350, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
//var listRect = listHolder.GetComponent<RectTransform>();
|
||||
//listRect.anchorMin = new Vector2(0, 1);
|
||||
//listRect.anchorMax = new Vector2(1, 1);
|
||||
UIFactory.SetLayoutElement(listHolder, minHeight: 150, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
// Left group (Children)
|
||||
|
@ -4,9 +4,10 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.Inspectors
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class ComponentCell : ButtonCell
|
||||
{
|
@ -5,7 +5,7 @@ using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.Inspectors
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class ComponentList : ButtonListHandler<Component, ComponentCell>
|
||||
{
|
||||
@ -21,7 +21,8 @@ namespace UnityExplorer.UI.Inspectors
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
this.currentEntries.Clear();
|
||||
RefreshData();
|
||||
ScrollPool.Refresh(true, true);
|
||||
}
|
||||
|
||||
private bool CheckShouldDisplay(Component _, string __) => true;
|
@ -5,8 +5,9 @@ using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.UI.Inspectors
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class GameObjectControls
|
||||
{
|
||||
@ -232,6 +233,12 @@ namespace UnityExplorer.UI.Inspectors
|
||||
}
|
||||
}
|
||||
|
||||
private void OnExploreButtonClicked()
|
||||
{
|
||||
var panel = UIManager.GetPanel<UI.Panels.ObjectExplorerPanel>(UIManager.Panels.ObjectExplorer);
|
||||
panel.SceneExplorer.JumpToTransform(this.Parent.GOTarget.transform);
|
||||
}
|
||||
|
||||
private void OnLayerDropdownChanged(int value)
|
||||
{
|
||||
GOTarget.layer = value;
|
||||
@ -462,7 +469,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
//UIFactory.SetLayoutElement(pathApplyBtn.Component.gameObject, minHeight: 25, minWidth: 120);
|
||||
//pathApplyBtn.OnClick += () => { OnPathEndEdit(PathInput.Text); };
|
||||
|
||||
PathInput.Component.onEndEdit.AddListener((string val) => { OnPathEndEdit(val); });
|
||||
PathInput.Component.GetOnEndEdit().AddListener((string val) => { OnPathEndEdit(val); });
|
||||
|
||||
// Title and update row
|
||||
|
||||
@ -478,7 +485,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
NameInput = UIFactory.CreateInputField(titleRow, "NameInput", "untitled");
|
||||
UIFactory.SetLayoutElement(NameInput.Component.gameObject, minHeight: 30, minWidth: 100, flexibleWidth: 9999);
|
||||
NameInput.Component.textComponent.fontSize = 15;
|
||||
NameInput.Component.onEndEdit.AddListener((string val) => { OnNameEndEdit(val); });
|
||||
NameInput.Component.GetOnEndEdit().AddListener((string val) => { OnNameEndEdit(val); });
|
||||
|
||||
// second row (toggles, instanceID, tag, buttons)
|
||||
|
||||
@ -515,7 +522,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
TagInput = UIFactory.CreateInputField(secondRow, "TagInput", "none");
|
||||
UIFactory.SetLayoutElement(TagInput.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999);
|
||||
TagInput.Component.textComponent.color = Color.white;
|
||||
TagInput.Component.onEndEdit.AddListener((string val) => { OnTagEndEdit(val); });
|
||||
TagInput.Component.GetOnEndEdit().AddListener((string val) => { OnTagEndEdit(val); });
|
||||
|
||||
// Instantiate
|
||||
var instantiateBtn = UIFactory.CreateButton(secondRow, "InstantiateBtn", "Instantiate", new Color(0.2f, 0.2f, 0.2f));
|
||||
@ -533,6 +540,12 @@ namespace UnityExplorer.UI.Inspectors
|
||||
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(thirdrow, false, false, true, true, 5, 0, 0, 0, 0, default);
|
||||
UIFactory.SetLayoutElement(thirdrow, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
// Inspect in Explorer button
|
||||
var explorerBtn = UIFactory.CreateButton(thirdrow, "ExploreBtn", "Show in Explorer", new Color(0.15f, 0.15f, 0.15f));
|
||||
UIFactory.SetLayoutElement(explorerBtn.Component.gameObject, minHeight: 25, minWidth: 100);
|
||||
explorerBtn.ButtonText.fontSize = 12;
|
||||
explorerBtn.OnClick += OnExploreButtonClicked;
|
||||
|
||||
// Scene
|
||||
var sceneLabel = UIFactory.CreateLabel(thirdrow, "SceneLabel", "Scene:", TextAnchor.MiddleLeft, Color.grey);
|
||||
UIFactory.SetLayoutElement(sceneLabel.gameObject, minHeight: 25, minWidth: 50);
|
||||
@ -547,7 +560,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
UIFactory.SetLayoutElement(layerLabel.gameObject, minHeight: 25, minWidth: 50);
|
||||
|
||||
var layerDrop = UIFactory.CreateDropdown(thirdrow, out LayerDropdown, "0", 14, OnLayerDropdownChanged);
|
||||
UIFactory.SetLayoutElement(layerDrop, minHeight: 25, minWidth: 120, flexibleWidth: 999);
|
||||
UIFactory.SetLayoutElement(layerDrop, minHeight: 25, minWidth: 110, flexibleWidth: 999);
|
||||
LayerDropdown.captionText.color = SignatureHighlighter.EnumGreen;
|
||||
if (layerToNames == null)
|
||||
GetLayerNames();
|
||||
@ -632,7 +645,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
var inputField = UIFactory.CreateInputField(rowObj, "InputField", "...");
|
||||
UIFactory.SetLayoutElement(inputField.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999);
|
||||
|
||||
inputField.Component.onEndEdit.AddListener((string value) => { OnTransformInputEndEdit(type, value); });
|
||||
inputField.Component.GetOnEndEdit().AddListener((string value) => { OnTransformInputEndEdit(type, value); });
|
||||
|
||||
var control = new TransformControl(type, inputField);
|
||||
|
215
src/Inspectors/InspectUnderMouse.cs
Normal file
215
src/Inspectors/InspectUnderMouse.cs
Normal file
@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.Inspectors.MouseInspectors;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public enum MouseInspectMode
|
||||
{
|
||||
World,
|
||||
UI
|
||||
}
|
||||
|
||||
public class InspectUnderMouse : UIPanel
|
||||
{
|
||||
public static InspectUnderMouse Instance { get; private set; }
|
||||
|
||||
private readonly WorldInspector worldInspector;
|
||||
private readonly UiInspector uiInspector;
|
||||
|
||||
public static bool Inspecting { get; set; }
|
||||
public static MouseInspectMode Mode { get; set; }
|
||||
|
||||
private static Vector3 lastMousePos;
|
||||
|
||||
public MouseInspectorBase CurrentInspector
|
||||
{
|
||||
get
|
||||
{
|
||||
switch (Mode)
|
||||
{
|
||||
case MouseInspectMode.UI:
|
||||
return uiInspector;
|
||||
case MouseInspectMode.World:
|
||||
return worldInspector;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// UIPanel
|
||||
public override string Name => "Inspect Under Mouse";
|
||||
public override UIManager.Panels PanelType => UIManager.Panels.MouseInspector;
|
||||
public override int MinWidth => -1;
|
||||
public override int MinHeight => -1;
|
||||
public override bool CanDragAndResize => false;
|
||||
public override bool NavButtonWanted => false;
|
||||
public override bool ShouldSaveActiveState => false;
|
||||
public override bool ShowByDefault => false;
|
||||
|
||||
internal Text objNameLabel;
|
||||
internal Text objPathLabel;
|
||||
internal Text mousePosLabel;
|
||||
|
||||
public InspectUnderMouse()
|
||||
{
|
||||
Instance = this;
|
||||
worldInspector = new WorldInspector();
|
||||
uiInspector = new UiInspector();
|
||||
}
|
||||
|
||||
public static void OnDropdownSelect(int index)
|
||||
{
|
||||
switch (index)
|
||||
{
|
||||
case 0: return;
|
||||
case 1: Instance.StartInspect(MouseInspectMode.World); break;
|
||||
case 2: Instance.StartInspect(MouseInspectMode.UI); break;
|
||||
}
|
||||
UIManager.MouseInspectDropdown.value = 0;
|
||||
}
|
||||
|
||||
public void StartInspect(MouseInspectMode mode)
|
||||
{
|
||||
Mode = mode;
|
||||
Inspecting = true;
|
||||
|
||||
CurrentInspector.OnBeginMouseInspect();
|
||||
|
||||
PanelDragger.ForceEnd();
|
||||
UIManager.NavBarRect.gameObject.SetActive(false);
|
||||
UIManager.PanelHolder.SetActive(false);
|
||||
|
||||
UIRoot.SetActive(true);
|
||||
}
|
||||
|
||||
internal void ClearHitData()
|
||||
{
|
||||
CurrentInspector.ClearHitData();
|
||||
|
||||
objNameLabel.text = "No hits...";
|
||||
objPathLabel.text = "";
|
||||
}
|
||||
|
||||
public void StopInspect()
|
||||
{
|
||||
CurrentInspector.OnEndInspect();
|
||||
ClearHitData();
|
||||
Inspecting = false;
|
||||
|
||||
UIManager.NavBarRect.gameObject.SetActive(true);
|
||||
UIManager.PanelHolder.SetActive(true);
|
||||
|
||||
var drop = UIManager.MouseInspectDropdown;
|
||||
if (drop.transform.Find("Dropdown List") is Transform list)
|
||||
drop.DestroyDropdownList(list.gameObject);
|
||||
|
||||
UIRoot.SetActive(false);
|
||||
}
|
||||
|
||||
private static float timeOfLastRaycast;
|
||||
|
||||
public void UpdateInspect()
|
||||
{
|
||||
if (InputManager.GetKeyDown(KeyCode.Escape))
|
||||
{
|
||||
StopInspect();
|
||||
return;
|
||||
}
|
||||
|
||||
if (InputManager.GetMouseButtonDown(0))
|
||||
{
|
||||
CurrentInspector.OnSelectMouseInspect();
|
||||
StopInspect();
|
||||
return;
|
||||
}
|
||||
|
||||
var mousePos = InputManager.MousePosition;
|
||||
if (mousePos != lastMousePos)
|
||||
UpdatePosition(mousePos);
|
||||
|
||||
if (!timeOfLastRaycast.OccuredEarlierThan(0.1f))
|
||||
return;
|
||||
timeOfLastRaycast = Time.realtimeSinceStartup;
|
||||
|
||||
CurrentInspector.UpdateMouseInspect(mousePos);
|
||||
}
|
||||
|
||||
internal void UpdatePosition(Vector2 mousePos)
|
||||
{
|
||||
lastMousePos = mousePos;
|
||||
|
||||
// use the raw mouse pos for the label
|
||||
mousePosLabel.text = $"<color=grey>Mouse Position:</color> {mousePos.ToString()}";
|
||||
|
||||
// constrain the mouse pos we use within certain bounds
|
||||
if (mousePos.x < 350)
|
||||
mousePos.x = 350;
|
||||
if (mousePos.x > Screen.width - 350)
|
||||
mousePos.x = Screen.width - 350;
|
||||
if (mousePos.y < Rect.rect.height)
|
||||
mousePos.y += Rect.rect.height + 10;
|
||||
else
|
||||
mousePos.y -= 10;
|
||||
|
||||
// calculate and set our UI position
|
||||
var inversePos = UIManager.CanvasRoot.transform.InverseTransformPoint(mousePos);
|
||||
UIRoot.transform.localPosition = new Vector3(inversePos.x, inversePos.y, 0);
|
||||
}
|
||||
|
||||
// UI Construction
|
||||
|
||||
protected internal override void DoSetDefaultPosAndAnchors()
|
||||
{
|
||||
Rect.anchorMin = Vector2.zero;
|
||||
Rect.anchorMax = Vector2.zero;
|
||||
Rect.pivot = new Vector2(0.5f, 1);
|
||||
Rect.sizeDelta = new Vector2(700, 150);
|
||||
}
|
||||
|
||||
public override void ConstructPanelContent()
|
||||
{
|
||||
// hide title bar
|
||||
this.titleBar.SetActive(false);
|
||||
this.UIRoot.transform.SetParent(UIManager.CanvasRoot.transform, false);
|
||||
|
||||
var inspectContent = UIFactory.CreateVerticalGroup(this.content, "InspectContent", true, true, true, true, 3, new Vector4(2, 2, 2, 2));
|
||||
UIFactory.SetLayoutElement(inspectContent, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
// Title text
|
||||
|
||||
var title = UIFactory.CreateLabel(inspectContent,
|
||||
"InspectLabel",
|
||||
"<b>Mouse Inspector</b> (press <b>ESC</b> to cancel)",
|
||||
TextAnchor.MiddleCenter);
|
||||
UIFactory.SetLayoutElement(title.gameObject, flexibleWidth: 9999);
|
||||
|
||||
mousePosLabel = UIFactory.CreateLabel(inspectContent, "MousePosLabel", "Mouse Position:", TextAnchor.MiddleCenter);
|
||||
|
||||
objNameLabel = UIFactory.CreateLabel(inspectContent, "HitLabelObj", "No hits...", TextAnchor.MiddleLeft);
|
||||
objNameLabel.horizontalOverflow = HorizontalWrapMode.Overflow;
|
||||
|
||||
objPathLabel = UIFactory.CreateLabel(inspectContent, "PathLabel", "", TextAnchor.MiddleLeft);
|
||||
objPathLabel.fontStyle = FontStyle.Italic;
|
||||
objPathLabel.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
|
||||
UIFactory.SetLayoutElement(objPathLabel.gameObject, minHeight: 75);
|
||||
|
||||
UIRoot.SetActive(false);
|
||||
}
|
||||
|
||||
public override void DoSaveToConfigElement() { }
|
||||
|
||||
public override string GetSaveDataFromConfigManager() => null;
|
||||
}
|
||||
}
|
@ -4,10 +4,11 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.ObjectPool;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Panels;
|
||||
|
||||
namespace UnityExplorer.UI.Inspectors
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public abstract class InspectorBase : IPooledObject
|
||||
{
|
@ -5,9 +5,9 @@ using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.UI.Inspectors;
|
||||
using UnityExplorer.UI.ObjectPool;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.Inspectors;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Panels;
|
||||
|
||||
namespace UnityExplorer
|
||||
@ -21,19 +21,6 @@ namespace UnityExplorer
|
||||
|
||||
public static float PanelWidth;
|
||||
|
||||
internal static void CloseAllTabs()
|
||||
{
|
||||
if (Inspectors.Any())
|
||||
{
|
||||
for (int i = Inspectors.Count - 1; i >= 0; i--)
|
||||
Inspectors[i].CloseInspector();
|
||||
|
||||
Inspectors.Clear();
|
||||
}
|
||||
|
||||
UIManager.SetPanelActive(UIManager.Panels.Inspector, false);
|
||||
}
|
||||
|
||||
public static void Inspect(object obj, CacheObjectBase sourceCache = null)
|
||||
{
|
||||
if (obj.IsNullOrDestroyed())
|
||||
@ -50,6 +37,11 @@ namespace UnityExplorer
|
||||
CreateInspector<ReflectionInspector>(obj, false, sourceCache);
|
||||
}
|
||||
|
||||
public static void Inspect(Type type)
|
||||
{
|
||||
CreateInspector<ReflectionInspector>(type, true);
|
||||
}
|
||||
|
||||
private static bool TryFocusActiveInspector(object target)
|
||||
{
|
||||
foreach (var inspector in Inspectors)
|
||||
@ -64,11 +56,6 @@ namespace UnityExplorer
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void Inspect(Type type)
|
||||
{
|
||||
CreateInspector<ReflectionInspector>(type, true);
|
||||
}
|
||||
|
||||
public static void SetInspectorActive(InspectorBase inspector)
|
||||
{
|
||||
UnsetActiveInspector();
|
||||
@ -87,6 +74,19 @@ namespace UnityExplorer
|
||||
}
|
||||
}
|
||||
|
||||
internal static void CloseAllTabs()
|
||||
{
|
||||
if (Inspectors.Any())
|
||||
{
|
||||
for (int i = Inspectors.Count - 1; i >= 0; i--)
|
||||
Inspectors[i].CloseInspector();
|
||||
|
||||
Inspectors.Clear();
|
||||
}
|
||||
|
||||
UIManager.SetPanelActive(UIManager.Panels.Inspector, false);
|
||||
}
|
||||
|
||||
private static void CreateInspector<T>(object target, bool staticReflection = false,
|
||||
CacheObjectBase sourceCache = null) where T : InspectorBase
|
||||
{
|
@ -4,10 +4,11 @@ using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI.ObjectPool;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Models;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
|
||||
namespace UnityExplorer.UI.Inspectors
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class InspectorTab : IPooledObject
|
||||
{
|
21
src/Inspectors/MouseInspectors/MouseInspectorBase.cs
Normal file
21
src/Inspectors/MouseInspectors/MouseInspectorBase.cs
Normal file
@ -0,0 +1,21 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.Inspectors.MouseInspectors
|
||||
{
|
||||
public abstract class MouseInspectorBase
|
||||
{
|
||||
public abstract void OnBeginMouseInspect();
|
||||
|
||||
public abstract void UpdateMouseInspect(Vector2 mousePos);
|
||||
|
||||
public abstract void OnSelectMouseInspect();
|
||||
|
||||
public abstract void ClearHitData();
|
||||
|
||||
public abstract void OnEndInspect();
|
||||
}
|
||||
}
|
142
src/Inspectors/MouseInspectors/UiInspector.cs
Normal file
142
src/Inspectors/MouseInspectors/UiInspector.cs
Normal file
@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.UI;
|
||||
using UnityExplorer.UI.Panels;
|
||||
|
||||
namespace UnityExplorer.Inspectors.MouseInspectors
|
||||
{
|
||||
public class UiInspector : MouseInspectorBase
|
||||
{
|
||||
public static readonly List<GameObject> LastHitObjects = new List<GameObject>();
|
||||
|
||||
private static GraphicRaycaster[] graphicRaycasters;
|
||||
|
||||
private static readonly List<GameObject> currentHitObjects = new List<GameObject>();
|
||||
|
||||
private static readonly List<Graphic> wasDisabledGraphics = new List<Graphic>();
|
||||
private static readonly List<CanvasGroup> wasDisabledCanvasGroups = new List<CanvasGroup>();
|
||||
private static readonly List<GameObject> objectsAddedCastersTo = new List<GameObject>();
|
||||
|
||||
public override void OnBeginMouseInspect()
|
||||
{
|
||||
SetupUIRaycast();
|
||||
InspectUnderMouse.Instance.objPathLabel.text = "";
|
||||
}
|
||||
|
||||
public override void ClearHitData()
|
||||
{
|
||||
currentHitObjects.Clear();
|
||||
}
|
||||
|
||||
public override void OnSelectMouseInspect()
|
||||
{
|
||||
LastHitObjects.Clear();
|
||||
LastHitObjects.AddRange(currentHitObjects);
|
||||
var panel = UIManager.GetPanel<UiInspectorResultsPanel>(UIManager.Panels.UIInspectorResults);
|
||||
panel.SetActive(true);
|
||||
panel.ShowResults();
|
||||
}
|
||||
|
||||
public override void UpdateMouseInspect(Vector2 mousePos)
|
||||
{
|
||||
currentHitObjects.Clear();
|
||||
|
||||
var ped = new PointerEventData(null)
|
||||
{
|
||||
position = mousePos
|
||||
};
|
||||
|
||||
foreach (var gr in graphicRaycasters)
|
||||
{
|
||||
if (!gr || !gr.canvas)
|
||||
continue;
|
||||
|
||||
var list = new List<RaycastResult>();
|
||||
RuntimeProvider.Instance.GraphicRaycast(gr, ped, list);
|
||||
if (list.Count > 0)
|
||||
{
|
||||
foreach (var hit in list)
|
||||
{
|
||||
if (hit.gameObject)
|
||||
currentHitObjects.Add(hit.gameObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentHitObjects.Any())
|
||||
InspectUnderMouse.Instance.objNameLabel.text = $"Click to view UI Objects under mouse: {currentHitObjects.Count}";
|
||||
else
|
||||
InspectUnderMouse.Instance.objNameLabel.text = $"No UI objects under mouse.";
|
||||
}
|
||||
|
||||
private static void SetupUIRaycast()
|
||||
{
|
||||
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Canvas)))
|
||||
{
|
||||
var canvas = obj.TryCast<Canvas>();
|
||||
if (!canvas || !canvas.enabled || !canvas.gameObject.activeInHierarchy)
|
||||
continue;
|
||||
if (!canvas.GetComponent<GraphicRaycaster>())
|
||||
{
|
||||
canvas.gameObject.AddComponent<GraphicRaycaster>();
|
||||
//ExplorerCore.Log("Added raycaster to " + canvas.name);
|
||||
objectsAddedCastersTo.Add(canvas.gameObject);
|
||||
}
|
||||
}
|
||||
|
||||
// recache Graphic Raycasters each time we start
|
||||
var casters = RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(GraphicRaycaster));
|
||||
graphicRaycasters = new GraphicRaycaster[casters.Length];
|
||||
for (int i = 0; i < casters.Length; i++)
|
||||
{
|
||||
graphicRaycasters[i] = casters[i].TryCast<GraphicRaycaster>();
|
||||
}
|
||||
|
||||
// enable raycastTarget on Graphics
|
||||
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(Graphic)))
|
||||
{
|
||||
var graphic = obj.TryCast<Graphic>();
|
||||
if (!graphic || !graphic.enabled || graphic.raycastTarget || !graphic.gameObject.activeInHierarchy)
|
||||
continue;
|
||||
graphic.raycastTarget = true;
|
||||
//ExplorerCore.Log("Enabled raycastTarget on " + graphic.name);
|
||||
wasDisabledGraphics.Add(graphic);
|
||||
}
|
||||
|
||||
// enable blocksRaycasts on CanvasGroups
|
||||
foreach (var obj in RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(CanvasGroup)))
|
||||
{
|
||||
var canvas = obj.TryCast<CanvasGroup>();
|
||||
if (!canvas || !canvas.gameObject.activeInHierarchy || canvas.blocksRaycasts)
|
||||
continue;
|
||||
canvas.blocksRaycasts = true;
|
||||
//ExplorerCore.Log("Enabled raycasts on " + canvas.name);
|
||||
wasDisabledCanvasGroups.Add(canvas);
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEndInspect()
|
||||
{
|
||||
foreach (var obj in objectsAddedCastersTo)
|
||||
{
|
||||
if (obj.GetComponent<GraphicRaycaster>() is GraphicRaycaster raycaster)
|
||||
GameObject.Destroy(raycaster);
|
||||
}
|
||||
|
||||
foreach (var graphic in wasDisabledGraphics)
|
||||
graphic.raycastTarget = false;
|
||||
|
||||
foreach (var canvas in wasDisabledCanvasGroups)
|
||||
canvas.blocksRaycasts = false;
|
||||
|
||||
objectsAddedCastersTo.Clear();
|
||||
wasDisabledCanvasGroups.Clear();
|
||||
wasDisabledGraphics.Clear();
|
||||
}
|
||||
}
|
||||
}
|
61
src/Inspectors/MouseInspectors/WorldInspector.cs
Normal file
61
src/Inspectors/MouseInspectors/WorldInspector.cs
Normal file
@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityExplorer.Inspectors.MouseInspectors
|
||||
{
|
||||
public class WorldInspector : MouseInspectorBase
|
||||
{
|
||||
private static Camera MainCamera;
|
||||
private static GameObject lastHitObject;
|
||||
|
||||
public override void OnBeginMouseInspect()
|
||||
{
|
||||
MainCamera = Camera.main;
|
||||
|
||||
if (!MainCamera)
|
||||
{
|
||||
ExplorerCore.LogWarning("No MainCamera found! Cannot inspect world!");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
public override void ClearHitData()
|
||||
{
|
||||
lastHitObject = null;
|
||||
}
|
||||
|
||||
public override void OnSelectMouseInspect()
|
||||
{
|
||||
InspectorManager.Inspect(lastHitObject);
|
||||
}
|
||||
|
||||
public override void UpdateMouseInspect(Vector2 mousePos)
|
||||
{
|
||||
var ray = MainCamera.ScreenPointToRay(mousePos);
|
||||
Physics.Raycast(ray, out RaycastHit hit, 1000f);
|
||||
|
||||
if (hit.transform)
|
||||
OnHitGameObject(hit.transform.gameObject);
|
||||
else if (lastHitObject)
|
||||
InspectUnderMouse.Instance.ClearHitData();
|
||||
}
|
||||
|
||||
internal void OnHitGameObject(GameObject obj)
|
||||
{
|
||||
if (obj != lastHitObject)
|
||||
{
|
||||
lastHitObject = obj;
|
||||
InspectUnderMouse.Instance.objNameLabel.text = $"<b>Click to Inspect:</b> <color=cyan>{obj.name}</color>";
|
||||
InspectUnderMouse.Instance.objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}";
|
||||
}
|
||||
}
|
||||
|
||||
public override void OnEndInspect()
|
||||
{
|
||||
// not needed
|
||||
}
|
||||
}
|
||||
}
|
@ -10,14 +10,13 @@ using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core.Runtime;
|
||||
using UnityExplorer.UI.CacheObject;
|
||||
using UnityExplorer.UI.CacheObject.Views;
|
||||
using UnityExplorer.UI.ObjectPool;
|
||||
using UnityExplorer.CacheObject;
|
||||
using UnityExplorer.CacheObject.Views;
|
||||
using UnityExplorer.UI.Panels;
|
||||
using UnityExplorer.UI.Utility;
|
||||
using UnityExplorer.UI.Widgets;
|
||||
using UnityExplorer.UI;
|
||||
|
||||
namespace UnityExplorer.UI.Inspectors
|
||||
namespace UnityExplorer.Inspectors
|
||||
{
|
||||
public class ReflectionInspector : InspectorBase, ICellPoolDataSource<CacheMemberCell>, ICacheObjectController
|
||||
{
|
||||
@ -48,6 +47,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
|
||||
public ScrollPool<CacheMemberCell> MemberScrollPool { get; private set; }
|
||||
|
||||
public InputFieldRef HiddenNameText;
|
||||
public Text NameText;
|
||||
public Text AssemblyText;
|
||||
private Toggle autoUpdateToggle;
|
||||
@ -56,6 +56,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
|
||||
private readonly Color disabledButtonColor = new Color(0.24f, 0.24f, 0.24f);
|
||||
private readonly Color enabledButtonColor = new Color(0.2f, 0.27f, 0.2f);
|
||||
|
||||
private readonly Dictionary<BindingFlags, ButtonRef> scopeFilterButtons = new Dictionary<BindingFlags, ButtonRef>();
|
||||
private readonly List<Toggle> memberTypeToggles = new List<Toggle>();
|
||||
private InputFieldRef filterInputField;
|
||||
@ -75,7 +76,6 @@ namespace UnityExplorer.UI.Inspectors
|
||||
private IEnumerator InitCoroutine()
|
||||
{
|
||||
yield return null;
|
||||
|
||||
LayoutRebuilder.ForceRebuildLayoutImmediate(InspectorPanel.Instance.ContentRect);
|
||||
}
|
||||
|
||||
@ -126,6 +126,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
currentBaseTabText = $"{prefix} {SignatureHighlighter.Parse(TargetType, false)}";
|
||||
Tab.TabText.text = currentBaseTabText;
|
||||
NameText.text = SignatureHighlighter.Parse(TargetType, true);
|
||||
HiddenNameText.Text = TargetType.FullName;
|
||||
|
||||
string asmText;
|
||||
if (TargetType.Assembly is AssemblyBuilder || string.IsNullOrEmpty(TargetType.Assembly.Location))
|
||||
@ -309,11 +310,8 @@ namespace UnityExplorer.UI.Inspectors
|
||||
|
||||
private void CalculateLayouts()
|
||||
{
|
||||
// Calculate sizes
|
||||
LeftGroupWidth = (int)Math.Max(200, (0.4f * InspectorManager.PanelWidth) - 5);// Math.Min(450f, 0.4f * InspectorManager.PanelWidth - 5));
|
||||
LeftGroupWidth = (int)Math.Max(200, (0.4f * InspectorManager.PanelWidth) - 5);
|
||||
RightGroupWidth = (int)Math.Max(200, InspectorManager.PanelWidth - LeftGroupWidth - 65);
|
||||
|
||||
//memberTitleLayout.minWidth = LeftGroupWidth;
|
||||
}
|
||||
|
||||
private void SetCellLayout(CacheObjectCell cell)
|
||||
@ -336,15 +334,34 @@ namespace UnityExplorer.UI.Inspectors
|
||||
|
||||
// Class name, assembly
|
||||
|
||||
NameText = UIFactory.CreateLabel(UIRoot, "Title", "not set", TextAnchor.MiddleLeft, fontSize: 17);
|
||||
UIFactory.SetLayoutElement(NameText.gameObject, minHeight: 25, flexibleHeight: 0);
|
||||
var titleHolder = UIFactory.CreateUIObject("TitleHolder", UIRoot);
|
||||
UIFactory.SetLayoutElement(titleHolder, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
NameText = UIFactory.CreateLabel(titleHolder, "VisibleTitle", "NotSet", TextAnchor.MiddleLeft);
|
||||
var namerect = NameText.GetComponent<RectTransform>();
|
||||
namerect.anchorMin = new Vector2(0, 0);
|
||||
namerect.anchorMax = new Vector2(1, 1);
|
||||
NameText.fontSize = 17;
|
||||
UIFactory.SetLayoutElement(NameText.gameObject, minHeight: 35, flexibleHeight: 0, minWidth: 300, flexibleWidth: 9999);
|
||||
|
||||
HiddenNameText = UIFactory.CreateInputField(titleHolder, "Title", "not set");
|
||||
var hiddenrect = HiddenNameText.Component.gameObject.GetComponent<RectTransform>();
|
||||
hiddenrect.anchorMin = new Vector2(0, 0);
|
||||
hiddenrect.anchorMax = new Vector2(1, 1);
|
||||
HiddenNameText.Component.readOnly = true;
|
||||
HiddenNameText.Component.lineType = InputField.LineType.MultiLineNewline;
|
||||
HiddenNameText.Component.gameObject.GetComponent<Image>().color = Color.clear;
|
||||
HiddenNameText.Component.textComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
|
||||
HiddenNameText.Component.textComponent.fontSize = 17;
|
||||
HiddenNameText.Component.textComponent.color = Color.clear;
|
||||
UIFactory.SetLayoutElement(HiddenNameText.Component.gameObject, minHeight: 35, flexibleHeight: 0, flexibleWidth: 9999);
|
||||
|
||||
AssemblyText = UIFactory.CreateLabel(UIRoot, "AssemblyLabel", "not set", TextAnchor.MiddleLeft);
|
||||
UIFactory.SetLayoutElement(AssemblyText.gameObject, minHeight: 25, flexibleWidth: 9999);
|
||||
|
||||
ConstructUnityObjectRow();
|
||||
|
||||
mainContentHolder = UIFactory.CreateVerticalGroup(UIRoot, "MemberHolder", false, false, true, true, 5, new Vector4(2,2,2,2),
|
||||
mainContentHolder = UIFactory.CreateVerticalGroup(UIRoot, "MemberHolder", false, false, true, true, 5, new Vector4(2, 2, 2, 2),
|
||||
new Color(0.12f, 0.12f, 0.12f));
|
||||
UIFactory.SetLayoutElement(mainContentHolder, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
@ -354,7 +371,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
|
||||
// Member scroll pool
|
||||
|
||||
var memberBorder = UIFactory.CreateVerticalGroup(mainContentHolder, "ScrollPoolHolder", false, false, true, true, padding: new Vector4(2,2,2,2),
|
||||
var memberBorder = UIFactory.CreateVerticalGroup(mainContentHolder, "ScrollPoolHolder", false, false, true, true, padding: new Vector4(2, 2, 2, 2),
|
||||
bgColor: new Color(0.05f, 0.05f, 0.05f));
|
||||
UIFactory.SetLayoutElement(memberBorder, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
@ -482,7 +499,7 @@ namespace UnityExplorer.UI.Inspectors
|
||||
|
||||
private void SetUnityTargets()
|
||||
{
|
||||
if (!typeof(UnityEngine.Object).IsAssignableFrom(TargetType))
|
||||
if (StaticOnly || !typeof(UnityEngine.Object).IsAssignableFrom(TargetType))
|
||||
{
|
||||
unityObjectRow.SetActive(false);
|
||||
textureViewer.SetActive(false);
|
||||
@ -619,12 +636,15 @@ namespace UnityExplorer.UI.Inspectors
|
||||
|
||||
// Actual texture viewer
|
||||
|
||||
var imageObj = UIFactory.CreateUIObject("TextureViewerImage", textureViewer);
|
||||
textureImage = imageObj.AddComponent<Image>();
|
||||
textureImageLayout = textureImage.gameObject.AddComponent<LayoutElement>();
|
||||
var imageViewport = UIFactory.CreateVerticalGroup(textureViewer, "Viewport", false, false, true, true);
|
||||
imageViewport.GetComponent<Image>().color = Color.white;
|
||||
imageViewport.AddComponent<Mask>().showMaskGraphic = false;
|
||||
|
||||
var imageObj = UIFactory.CreateUIObject("Image", imageViewport);
|
||||
var fitter = imageObj.AddComponent<ContentSizeFitter>();
|
||||
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
|
||||
textureImage = imageObj.AddComponent<Image>();
|
||||
textureImageLayout = UIFactory.SetLayoutElement(imageObj, flexibleWidth: 9999, flexibleHeight: 9999);
|
||||
|
||||
textureViewer.SetActive(false);
|
||||
}
|
@ -1,119 +0,0 @@
|
||||
#if BIE
|
||||
using BepInEx;
|
||||
using BepInEx.Logging;
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Loader.BIE;
|
||||
using UnityEngine;
|
||||
using UnityExplorer.Core;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityExplorer.Core.Input;
|
||||
#if CPP
|
||||
using BepInEx.IL2CPP;
|
||||
using UnhollowerRuntimeLib;
|
||||
#endif
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
|
||||
|
||||
public class ExplorerBepInPlugin :
|
||||
#if MONO
|
||||
BaseUnityPlugin
|
||||
#else
|
||||
BasePlugin
|
||||
#endif
|
||||
, IExplorerLoader
|
||||
{
|
||||
public static ExplorerBepInPlugin Instance;
|
||||
|
||||
public ManualLogSource LogSource
|
||||
#if MONO
|
||||
=> Logger;
|
||||
#else
|
||||
=> Log;
|
||||
#endif
|
||||
|
||||
public ConfigHandler ConfigHandler => _configHandler;
|
||||
private BepInExConfigHandler _configHandler;
|
||||
|
||||
public Harmony HarmonyInstance => s_harmony;
|
||||
private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
|
||||
|
||||
public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME);
|
||||
public string ConfigFolder => Path.Combine(Paths.ConfigPath, ExplorerCore.NAME);
|
||||
|
||||
public Action<object> OnLogMessage => LogSource.LogMessage;
|
||||
public Action<object> OnLogWarning => LogSource.LogWarning;
|
||||
public Action<object> OnLogError => LogSource.LogError;
|
||||
|
||||
// Init common to Mono and Il2Cpp
|
||||
internal void UniversalInit()
|
||||
{
|
||||
Instance = this;
|
||||
_configHandler = new BepInExConfigHandler();
|
||||
ExplorerCore.Init(this);
|
||||
}
|
||||
|
||||
#if MONO // Mono
|
||||
internal void Awake()
|
||||
{
|
||||
UniversalInit();
|
||||
}
|
||||
|
||||
#else // Il2Cpp
|
||||
public override void Load()
|
||||
{
|
||||
UniversalInit();
|
||||
}
|
||||
#endif
|
||||
|
||||
public void SetupCursorPatches()
|
||||
{
|
||||
try
|
||||
{
|
||||
this.HarmonyInstance.PatchAll();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(EventSystem), "current", MethodType.Setter)]
|
||||
public class PATCH_EventSystem_current
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_EventSystem_set_current(ref EventSystem value)
|
||||
{
|
||||
CursorUnlocker.Prefix_EventSystem_set_current(ref value);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Cursor), "lockState", MethodType.Setter)]
|
||||
public class PATCH_Cursor_lockState
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_set_lockState(ref CursorLockMode value)
|
||||
{
|
||||
CursorUnlocker.Prefix_set_lockState(ref value);
|
||||
}
|
||||
}
|
||||
|
||||
[HarmonyPatch(typeof(Cursor), "visible", MethodType.Setter)]
|
||||
public class PATCH_Cursor_visible
|
||||
{
|
||||
[HarmonyPrefix]
|
||||
public static void Prefix_set_visible(ref bool value)
|
||||
{
|
||||
CursorUnlocker.Prefix_set_visible(ref value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
@ -21,11 +21,7 @@ namespace UnityExplorer.Loader.BIE
|
||||
|
||||
public override void RegisterConfigElement<T>(ConfigElement<T> config)
|
||||
{
|
||||
object[] tags = null;
|
||||
if (config.IsInternal)
|
||||
tags = new[] { "Advanced" };
|
||||
|
||||
var entry = Config.Bind(CTG_NAME, config.Name, config.Value, new ConfigDescription(config.Description, null, tags));
|
||||
var entry = Config.Bind(CTG_NAME, config.Name, config.Value, config.Description);
|
||||
|
||||
entry.SettingChanged += (object o, EventArgs e) =>
|
||||
{
|
77
src/Loader/BepInEx/ExplorerBepInPlugin.cs
Normal file
77
src/Loader/BepInEx/ExplorerBepInPlugin.cs
Normal file
@ -0,0 +1,77 @@
|
||||
#if BIE
|
||||
using BepInEx;
|
||||
using BepInEx.Logging;
|
||||
using HarmonyLib;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.Loader.BIE;
|
||||
#if CPP
|
||||
using BepInEx.IL2CPP;
|
||||
using UnhollowerRuntimeLib;
|
||||
#endif
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
|
||||
|
||||
public class ExplorerBepInPlugin :
|
||||
#if MONO
|
||||
BaseUnityPlugin
|
||||
#else
|
||||
BasePlugin
|
||||
#endif
|
||||
, IExplorerLoader
|
||||
{
|
||||
public static ExplorerBepInPlugin Instance;
|
||||
|
||||
public ManualLogSource LogSource
|
||||
#if MONO
|
||||
=> Logger;
|
||||
#else
|
||||
=> Log;
|
||||
#endif
|
||||
|
||||
public string UnhollowedModulesFolder => Path.Combine(Paths.BepInExRootPath, "unhollowed");
|
||||
|
||||
public ConfigHandler ConfigHandler => _configHandler;
|
||||
private BepInExConfigHandler _configHandler;
|
||||
|
||||
public Harmony HarmonyInstance => s_harmony;
|
||||
private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
|
||||
|
||||
public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME);
|
||||
|
||||
public Action<object> OnLogMessage => LogSource.LogMessage;
|
||||
public Action<object> OnLogWarning => LogSource.LogWarning;
|
||||
public Action<object> OnLogError => LogSource.LogError;
|
||||
|
||||
private void Init()
|
||||
{
|
||||
Instance = this;
|
||||
_configHandler = new BepInExConfigHandler();
|
||||
ExplorerCore.Init(this);
|
||||
}
|
||||
|
||||
#if MONO // Mono
|
||||
internal void Awake()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
|
||||
#else // Il2Cpp
|
||||
public override void Load()
|
||||
{
|
||||
Init();
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
#endif
|
@ -9,14 +9,12 @@ namespace UnityExplorer
|
||||
public interface IExplorerLoader
|
||||
{
|
||||
string ExplorerFolder { get; }
|
||||
string UnhollowedModulesFolder { get; }
|
||||
|
||||
string ConfigFolder { get; }
|
||||
ConfigHandler ConfigHandler { get; }
|
||||
|
||||
Action<object> OnLogMessage { get; }
|
||||
Action<object> OnLogWarning { get; }
|
||||
Action<object> OnLogError { get; }
|
||||
|
||||
void SetupCursorPatches();
|
||||
}
|
||||
}
|
||||
|
@ -1,79 +0,0 @@
|
||||
#if ML
|
||||
using System;
|
||||
using System.IO;
|
||||
using MelonLoader;
|
||||
using UnityEngine;
|
||||
using UnityEngine.EventSystems;
|
||||
using UnityExplorer;
|
||||
using UnityExplorer.Core;
|
||||
using UnityExplorer.Core.Config;
|
||||
using UnityExplorer.Core.Input;
|
||||
using UnityExplorer.Loader.ML;
|
||||
using HarmonyLib;
|
||||
|
||||
[assembly: MelonInfo(typeof(ExplorerMelonMod), ExplorerCore.NAME, ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
|
||||
[assembly: MelonGame(null, null)]
|
||||
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.UNIVERSAL)]
|
||||
[assembly: MelonColor(ConsoleColor.DarkCyan)]
|
||||
|
||||
namespace UnityExplorer
|
||||
{
|
||||
public class ExplorerMelonMod : MelonMod, IExplorerLoader
|
||||
{
|
||||
public static ExplorerMelonMod Instance;
|
||||
|
||||
public string ExplorerFolder => Path.Combine("Mods", ExplorerCore.NAME);
|
||||
public string ConfigFolder => ExplorerFolder;
|
||||
|
||||
public ConfigHandler ConfigHandler => _configHandler;
|
||||
public MelonLoaderConfigHandler _configHandler;
|
||||
|
||||
public Action<object> OnLogMessage => MelonLogger.Msg;
|
||||
public Action<object> OnLogWarning => MelonLogger.Warning;
|
||||
public Action<object> OnLogError => MelonLogger.Error;
|
||||
|
||||
public override void OnApplicationStart()
|
||||
{
|
||||
Instance = this;
|
||||
_configHandler = new MelonLoaderConfigHandler();
|
||||
|
||||
ExplorerCore.Init(this);
|
||||
}
|
||||
|
||||
public void SetupCursorPatches()
|
||||
{
|
||||
try
|
||||
{
|
||||
PrefixProperty(typeof(Cursor),
|
||||
"lockState",
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_lockState))));
|
||||
|
||||
PrefixProperty(typeof(Cursor),
|
||||
"visible",
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_set_visible))));
|
||||
|
||||
PrefixProperty(typeof(EventSystem),
|
||||
"current",
|
||||
new HarmonyMethod(typeof(CursorUnlocker).GetMethod(nameof(CursorUnlocker.Prefix_EventSystem_set_current))));
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ExplorerCore.Log($"Exception setting up Harmony patches:\r\n{ex.ReflectionExToString()}");
|
||||
}
|
||||
}
|
||||
|
||||
private void PrefixProperty(Type type, string property, HarmonyMethod prefix)
|
||||
{
|
||||
try
|
||||
{
|
||||
var prop = type.GetProperty(property);
|
||||
HarmonyInstance.Patch(prop.GetSetMethod(), prefix: prefix);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
ExplorerCore.Log($"Unable to patch {type.Name}.set_{property}: {e.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user