Compare commits

...

143 Commits

Author SHA1 Message Date
adfa29e63c Bump version 2021-07-15 05:20:31 +10:00
3ffdcea73b Force all devices to always be supported by InputSystem 2021-07-15 05:20:26 +10:00
dfd55260a8 Fallback to null check loading if unityVersion throws an exception 2021-07-15 05:19:48 +10:00
29c78dc5a6 Manually get Major and Minor instead of using Version class 2021-07-11 23:22:33 +10:00
bf59d9d6cd Handle cases where Unity doesn't include 'f' suffix on version
Not sure if this ever happens but just in case
2021-07-11 18:19:09 +10:00
bb0c59534a Bump version 2021-07-11 18:11:18 +10:00
802bb722bc Use Application.unityVersion to load appropriate AssetBundle
And fix issue with Dropdowns and Time input label not refreshing properly on launch
2021-07-11 18:11:14 +10:00
9ca992b0d7 Fix incorrect config description example 2021-07-07 23:27:43 +10:00
5f3b3a6870 cleanup -noci 2021-07-07 23:21:40 +10:00
f5bce439cb Favor public GetEnumerator over private. Use GetActualType. Improve logging. 2021-07-06 16:42:20 +10:00
e2b2c9038a Add 'other' issue template -noci 2021-07-05 19:05:04 +10:00
fbefccd6b7 Update issue template -noci 2021-07-02 23:32:02 +10:00
271c91f0d0 Update README.md 2021-07-02 20:03:41 +10:00
eb221bd868 Bump version 2021-07-02 18:45:09 +10:00
5b516eb4cc Add assembly name to class search result labels 2021-07-02 18:45:03 +10:00
601567f9d2 Remove arbitrary static restriction on class search 2021-07-02 17:53:12 +10:00
7ff508b874 [IL2CPP] Support private interface implementations on IDictionary and IEnumerable 2021-07-02 17:45:18 +10:00
09a7cd35cf Remove known issues from readme -noci 2021-07-02 17:35:34 +10:00
db4a338d26 Update issue template -noci 2021-07-02 17:33:41 +10:00
c08e02057c Create enhancement.yaml 2021-07-02 17:31:36 +10:00
362fcdc51a Update bug_report.yaml 2021-07-02 17:22:24 +10:00
73bd172e4d Update bug_report.yaml 2021-07-02 17:16:09 +10:00
454d3bd0b4 Add bug report issue template 2021-07-02 17:10:26 +10:00
f815a13d9a Namespace cleanup, move some categories out of UI namespace 2021-06-30 07:49:58 +10:00
65c4d49274 Update readme 2021-06-26 18:08:21 +10:00
d99137526e Fix InspectUnderMouse breaking ScrollPool in rare cases 2021-06-22 20:39:46 +10:00
92447b55cd Update README.md 2021-06-22 19:48:41 +10:00
87d5d5a2de Bump version 2021-06-22 19:46:14 +10:00
08cff3386b Fix issues with Il2Cpp nullables 2021-06-22 19:46:09 +10:00
6033200579 Don't use WaitForEndOfFrame in SrollPool init coroutine 2021-06-22 19:45:57 +10:00
94ec1c4908 Update line numbers on panel resize 2021-06-21 19:50:11 +10:00
7a400e762c Cleanup 2021-06-21 19:49:44 +10:00
67f9f744bb Handle Unity 2021+ InputField.onEndEdit change 2021-06-21 19:26:05 +10:00
7a59f9a2a1 Update info about startup script -noci 2021-06-20 19:10:44 +10:00
9b42eef1b9 Line numbers and startup script 2021-06-20 19:06:52 +10:00
830000b019 Bump version 2021-06-20 19:06:33 +10:00
34910ab273 Bump version 2021-06-14 18:56:56 +10:00
86b036095e Allow panels to be dragged further outside the game window 2021-06-14 18:56:51 +10:00
b57e5be2e6 Add GameObject.activeSelf toggle to TransformTree, adjust UI Toggle design 2021-06-14 18:43:26 +10:00
2d8ae45814 Cleanup 2021-06-14 18:43:08 +10:00
66dc262a68 Move inner exception null check outside IL2CPP ppd 2021-06-11 18:12:14 +10:00
4342901206 Prevent null exceptions being used in ReflectionExToString 2021-06-11 17:36:35 +10:00
58b7c72a5c Allow UI inspect without main camera 2021-06-11 17:36:17 +10:00
623dc7b7be Clarify VS build instructions 2021-06-10 18:14:33 +10:00
e6f4939cc9 Update README.md 2021-06-07 19:50:36 +10:00
7a539ba78b Bump version 2021-06-07 19:28:25 +10:00
0d10f94eb5 Use Paths.BepInExRootPath, and cleanup dirs 2021-06-07 19:27:39 +10:00
91671bf243 Failsafe in case Component is null from GetComponents? 2021-06-07 19:27:09 +10:00
a72877befb Make AllTypes protected, force using GetTypeByName 2021-06-07 19:26:46 +10:00
16335c1bc4 Auto-cleanup 2021-06-05 19:36:09 +10:00
8fab9e6268 Update readme -noci 2021-06-03 17:50:48 +10:00
fdd9039cca Update README.md 2021-06-03 17:47:57 +10:00
dcca980635 Merge branch 'master' of https://github.com/sinai-dev/Explorer 2021-06-03 17:19:01 +10:00
bfcab8248e Bump version 2021-06-03 17:18:52 +10:00
97c20144f1 Fix string/comment state being incorrect if match ended at last character 2021-06-03 17:18:46 +10:00
aaab10a0a0 Do auto-indent before highlighting so the characters are in the correct position 2021-06-03 17:18:11 +10:00
b42a8dbe6a Update readme -noci 2021-06-03 02:48:14 +10:00
d7008db22e Update README.md 2021-06-02 16:25:12 +10:00
8c1913fe80 Update README.md 2021-06-02 15:33:19 +10:00
8d8c9ac7c9 Use whitespace as delimiter in completion composition, handle string/comment state with Lexer 2021-06-01 16:40:51 +10:00
f35beeaf58 Bump version 2021-06-01 16:00:56 +10:00
d150ff3455 Fix minor issues with CSConsole delimiter logic 2021-06-01 16:00:52 +10:00
c4fa0d6bcd Add better time logging to reflection init 2021-06-01 16:00:18 +10:00
a5f56cf5a3 Prevent very large Texture images from overflowing outside the Texture Viewer 2021-05-31 19:10:05 +10:00
8c822b2ee9 Simplify ci workflow 2021-05-30 04:50:37 +10:00
4681b7e192 Update README -noci 2021-05-29 19:46:39 +10:00
97093733d8 Update README -noci 2021-05-29 19:42:12 +10:00
615f77979f Update README -noci 2021-05-29 19:41:14 +10:00
ba3cf970d9 Update README.md 2021-05-29 19:30:08 +10:00
b2fb571b18 Testing ci skip -noci 2021-05-29 19:15:28 +10:00
67ce6f946a Update dotnet.yml 2021-05-29 19:13:30 +10:00
1b82ccb49f Bump version 2021-05-29 19:07:44 +10:00
480eb5afd5 Use HarmonyX NuGet package 2021-05-29 19:07:26 +10:00
88ea2a09c9 Update README.md 2021-05-29 18:46:17 +10:00
a678aa4d78 Update dotnet.yml 2021-05-29 18:30:52 +10:00
eaf478e314 Using temporary manual HarmonyX reference 2021-05-29 18:27:40 +10:00
d391968b32 Update dotnet.yml 2021-05-29 17:50:59 +10:00
70349ad7c7 Update dotnet.yml 2021-05-29 17:48:02 +10:00
78f2d1070f Update dotnet.yml 2021-05-29 17:46:59 +10:00
51307563ab Update dotnet.yml 2021-05-29 17:41:08 +10:00
185d1aaa0f Update dotnet.yml 2021-05-29 17:38:21 +10:00
3d6e8fcbf8 Update dotnet.yml 2021-05-29 17:36:56 +10:00
1daf4fade4 Use HarmonyX NuGet instead of submodule 2021-05-29 17:36:07 +10:00
d92fb3f83f Update dotnet.yml 2021-05-29 17:27:52 +10:00
fe24b68fe2 Update dotnet.yml 2021-05-29 17:24:53 +10:00
6bf92b9a96 Update dotnet.yml 2021-05-29 17:20:42 +10:00
9f1cab019d Create dotnet.yml 2021-05-29 17:18:09 +10:00
a131404ac7 Cleanup 2021-05-29 14:50:27 +10:00
773900d749 Fix CacheProperty not resetting Exception state when it has arguments 2021-05-29 14:50:21 +10:00
342fc6bdb8 Maintain last timeScale value 2021-05-28 18:48:55 +10:00
e85ea6ac3a Make Il2CppProvider actually process FixedUpdate coroutines 2021-05-28 18:23:45 +10:00
af889e64cb Fix exception when inspecting UnityObject classes with static reflection 2021-05-28 18:23:30 +10:00
0274022ce4 Make sure WaitForEndOfFrame object is never null 2021-05-28 18:23:07 +10:00
211576e0f8 Fallback to LateUpdate if OnPostRender listener failed 2021-05-28 18:22:44 +10:00
3e42d7479f Update README.md 2021-05-28 16:59:22 +10:00
df4dea20c1 Update README.md 2021-05-28 16:55:01 +10:00
ece0c43067 Add Time.timeScale helper on main navbar 2021-05-28 16:39:42 +10:00
6311c8d09a Bump version 2021-05-28 15:51:09 +10:00
04739d0be8 Separate default reflection blacklist from user list, add try/catch 2021-05-28 15:51:03 +10:00
a46acba265 Better JumpToIndex height calculation 2021-05-27 19:44:17 +10:00
5515b2eae4 Bump version 2021-05-27 19:31:01 +10:00
9992029e28 Set DataViewInfo struct back to array in rebuild, cleanup 2021-05-27 19:30:55 +10:00
14105785f0 Fix SceneExplorer not properly detecting scene changes sometimes 2021-05-27 19:30:19 +10:00
365269b0dd Clear GameObject Component and Transform lists properly on close 2021-05-27 19:29:00 +10:00
0b973393d1 Cleanup 2021-05-27 19:28:22 +10:00
701d4431ae Delete explorerui.legacy.bundle.bak.5.3.8 2021-05-27 16:00:42 +10:00
bfa73bcb55 Cleanup Pool.cs 2021-05-26 19:42:56 +10:00
b0bbeb3cf8 Cleanup and fix small issue with JumpToIndex 2021-05-26 18:32:47 +10:00
1a26623080 Add option to disable EventSystem override 2021-05-26 18:02:10 +10:00
041f2938f7 Implement jumping to index in TransformTree 2021-05-26 17:42:31 +10:00
9e7bb1a625 Cleanup 2021-05-26 17:42:14 +10:00
36f23b7cdc Move SceneHandler.cs 2021-05-26 17:41:51 +10:00
b51b743df4 Prevent very niche recursion situation 2021-05-26 17:41:38 +10:00
805aff07cc Update README.md 2021-05-26 17:41:19 +10:00
bcdaf3b97e Bump version 2021-05-26 17:41:14 +10:00
cb8e947fdf Namespace/structure cleanup 2021-05-26 17:40:09 +10:00
c8899be3ae Bump version 2021-05-26 03:59:50 +10:00
cd5c69c965 Add timer debug to deobfuscation cache 2021-05-26 03:59:45 +10:00
5427312f18 Filter UnityExplorer objects from search results 2021-05-26 03:59:17 +10:00
eb7e80d910 Make sure Mouse Inspect dropdown list gets destroyed after option chosen 2021-05-26 03:59:08 +10:00
a54888ae3a Make DataViewInfo a struct instead of class 2021-05-25 15:46:30 +10:00
4f0553d293 Remove formatting from ToStringUtility 2021-05-23 19:34:32 +10:00
9f0f7f9b57 Prevent some niche exceptions with EventSystem 2021-05-23 18:33:19 +10:00
383c6f19e8 Update README.md 2021-05-23 16:20:09 +10:00
428fab28f9 Cleanup HideAndDontSave detection and support 2021-05-23 16:16:32 +10:00
760b2981ad Update README.md 2021-05-23 13:59:29 +10:00
eee7d6bcc4 Add ML 0.3.0 build 2021-05-23 13:58:26 +10:00
e270f205a1 Fix scroll pool inserting off-by-one 2021-05-20 20:22:45 +10:00
084aee617c Prevent null reference if Canvas isn't created yet 2021-05-19 22:21:08 +10:00
d0e508727a Prevent deobfuscation crash from using Il2CppType.From, and use faster method anyway 2021-05-19 21:26:44 +10:00
a9a53ba924 Force load all Unhollowed DLLs, use Assembly.LoadFile instead of .Load, blacklist some more types 2021-05-19 20:48:34 +10:00
3cd9819790 remove backup libs 2021-05-19 19:47:13 +10:00
5abfa3da67 Better EntryType checking for enumerables and dicts 2021-05-19 19:24:33 +10:00
e5d2d29a47 Update README.md 2021-05-19 19:24:06 +10:00
6a47e542e5 Update Extensions.cs 2021-05-19 18:46:01 +10:00
f1b83e7c9e Cleanup 2021-05-19 18:45:53 +10:00
44c6503ae2 Update unhollowed libs 2021-05-19 18:45:19 +10:00
6970dcbbc7 Merge pull request #68 from sinai-dev/4.0.0-alpha
4.0 hotfixes
2021-05-18 21:20:09 +10:00
ac9c2d5286 Use recursive GetGenericArguments to catch unusual type structures, cleanup InteractiveList value caching 2021-05-18 20:55:18 +10:00
496a5de55e Update README.md 2021-05-18 20:44:42 +10:00
b062924af7 Add support for writing to IList<T>'s which don't implement IList 2021-05-18 20:43:51 +10:00
5aef8ddc99 Fix UIPanels being broken after resolution changes, better checks on size/position 2021-05-18 19:55:27 +10:00
c134c1752e Update README.md 2021-05-18 01:25:57 +10:00
120 changed files with 2425 additions and 1353 deletions

62
.github/ISSUE_TEMPLATE/bug_report.yaml vendored Normal file
View File

@ -0,0 +1,62 @@
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 0.4+ IL2CPP
- MelonLoader 0.4+ Mono
- MelonLoader 0.3 IL2CPP
- MelonLoader 0.3 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
View File

@ -0,0 +1 @@
blank_issues_enabled: false

18
.github/ISSUE_TEMPLATE/enhancement.yaml vendored Normal file
View 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
View File

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

113
.github/workflows/dotnet.yml vendored Normal file
View File

@ -0,0 +1,113 @@
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/*
# MelonLoader 0.3.0 Il2Cpp
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_MLLegacy_Cpp
- uses: actions/upload-artifact@v2
with:
name: UnityExplorer.MelonLoader_Legacy.Il2Cpp
path: ./Release/UnityExplorer.MelonLoader_Legacy.Il2Cpp/*
# MelonLoader 0.3.0 Mono
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_MLLegacy_Mono
- uses: actions/upload-artifact@v2
with:
name: UnityExplorer.MelonLoader_Legacy.Mono
path: ./Release/UnityExplorer.MelonLoader_Legacy.Mono/*
# Standalone Il2Cpp
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_STANDALONE_Cpp
- 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
View File

@ -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

116
README.md
View File

@ -3,45 +3,45 @@
</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>
<p align="center">
⚡ UnityExplorer is on <a href="https://thunderstore.io/package/sinai-dev/UnityExplorer/">Thunderstore</a>! (and as <a href="https://gtfo.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP/">IL2CPP</a>)
</p>
## Releases [![](https://img.shields.io/github/release/sinai-dev/UnityExplorer.svg?label=release%20notes)](../../releases/latest) [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/total.svg)](../../releases) [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/latest/total.svg)](../../releases/latest)
# Releases [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/total.svg)](../../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) |
[![](https://img.shields.io/github/release/sinai-dev/UnityExplorer.svg?label=version)](../../releases/latest) [![](https://img.shields.io/github/workflow/status/sinai-dev/UnityExplorer/Build%20UnityExplorer)](https://github.com/sinai-dev/UnityExplorer/actions) [![](https://img.shields.io/github/downloads/sinai-dev/UnityExplorer/latest/total.svg)](../../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.
## BepInEx
## How to install
| 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) |
### BepInEx
1. Take the `UnityExplorer.BIE.[version].dll` file and put it in `BepInEx\plugins\`
2. In IL2CPP, you will need to download the [Unity libs](https://github.com/LavaGang/Unity-Runtime-Libraries) for the game's Unity version, create a folder `BepInEx\unity-libs\`, then extract the Unity libs into this folder.
1. 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.
<i>Note: BepInEx 6 is obtainable via [BepisBuilds](https://builds.bepis.io/projects/bepinex_be)</i>
### MelonLoader
## MelonLoader
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.
| 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) |
| ML 0.3 | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader_Legacy.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader_Legacy.Mono.zip) |
### Standalone
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 +50,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 +60,61 @@ 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.
See the "Help" dropdown in the C# console menu for more detailed information.
* 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.
### 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
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):
Building the project should be straight-forward, the references are all inside the `lib\` folder.
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.
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.
For Visual Studio:
## Acknowledgments
0. Clone the repository and run `git submodule update --init --recursive` to get the submodules.
1. Open the `src\UnityExplorer.sln` project.
2. Build `mcs` (Release/AnyCPU, you may need to run `nuget restore mcs.sln`), and if using IL2CPP then build `Il2CppAssemblyUnhollower` (Release/AnyCPU) as well.
3. Build the UnityExplorer release(s) you want to use, either by selecting the config as the Active Config, or batch-building.
# Acknowledgments
* [ManlyMarco](https://github.com/ManlyMarco) for [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor) \[[license](THIRDPARTY_LICENSES.md#runtimeunityeditor-license)\], the ScriptEvaluator from RUE's REPL console was used as the base for UnityExplorer's C# console.
* [denikson](https://github.com/denikson) (aka Horse) for [mcs-unity](https://github.com/denikson/mcs-unity) \[no license\], used as the `Mono.CSharp` reference for the C# Console.

Submodule lib/HarmonyX deleted from 64462b3e31

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -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

View File

@ -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,9 +200,7 @@ namespace UnityExplorer.UI.CSConsole
}
else
{
// The input was not recognized as an evaluation. Compile the code.
Evaluator.Run(input);
// The compiled code was not REPL, so it was a using directive or it defined classes.
string output = ScriptEvaluator._textWriter.ToString();
var outputSplit = output.Split('\n');
@ -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)
{
if (AutoCompleteModal.CheckEscape(Completer))
{
OnAutocompleteEscaped();
return;
}
if (!settingCaretCoroutine && EnableSuggestions && caretMoved)
{
if (caretMoved)
AutoCompleteModal.Instance.ReleaseOwnership(Completer);
//Completer.CheckAutocompletes();
}
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,25 +402,29 @@ 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;
if (string.IsNullOrEmpty(Input.Text))
{
Panel.HighlightText.text = "";
Panel.LineNumberText.text = "1";
return false;
}
// Calculate visible text if necessary
if (Input.Rect.rect.height > Panel.InputScroll.ViewportRect.rect.height)
{
topLine = -1;
// 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.InputScroll.ViewportRect.rect.height;
var viewportMax = viewportMin - Panel.InputScroller.ViewportRect.rect.height;
for (int i = 0; i < Input.TextGenerator.lineCount; i++)
{
@ -385,14 +440,53 @@ namespace UnityExplorer.UI.CSConsole
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)
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.

View File

@ -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,28 @@ 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)
{
if (IsNewLine(input[matchEndIdx]))
break;
matchEndIdx++;
}
}
// check caretIdx to determine inStringOrComment state
if (caretIdx >= match.startIndex && (caretIdx <= matchEndIdx || (caretIdx >= input.Length && matchEndIdx >= input.Length - 1)))
caretInStringOrComment = match.isStringOrComment;
}
// Append trailing unhighlighted input
@ -150,6 +168,7 @@ namespace UnityExplorer.UI.CSConsole
startIndex = startIndex,
endIndex = CommittedIndex,
htmlColorTag = lexer.ColorTag,
isStringOrComment = lexer is StringLexer || lexer is CommentLexer,
};
break;
}

View File

@ -2,7 +2,7 @@
using System.Linq;
using UnityEngine;
namespace UnityExplorer.UI.CSConsole.Lexers
namespace UnityExplorer.CSConsole.Lexers
{
public class CommentLexer : Lexer
{

View File

@ -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)

View File

@ -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
{

View File

@ -1,6 +1,6 @@
using UnityEngine;
namespace UnityExplorer.UI.CSConsole.Lexers
namespace UnityExplorer.CSConsole.Lexers
{
public class NumberLexer : Lexer
{

View File

@ -1,7 +1,7 @@
using System.Collections.Generic;
using UnityEngine;
namespace UnityExplorer.UI.CSConsole.Lexers
namespace UnityExplorer.CSConsole.Lexers
{
public class StringLexer : Lexer
{

View File

@ -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);

View File

@ -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
{

View File

@ -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
{

View File

@ -3,9 +3,9 @@ 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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -5,12 +5,12 @@ 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
{
@ -246,7 +246,6 @@ namespace UnityExplorer.UI.CacheObject
var sig = GetSig(member);
//ExplorerCore.Log($"Trying to cache member {sig}...");
//ExplorerCore.Log(member.DeclaringType.FullName + "." + member.Name);
CacheMember cached;
Type returnType;
@ -324,7 +323,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)
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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;

View File

@ -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
{

View File

@ -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
{

View File

@ -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);

View File

@ -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
{

View File

@ -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);
if (!IsWritableGenericIList)
{
RefIList[index] = value;
}
else
{
genericIndexer.SetValue(CurrentOwner.Value, value, new object[] { index });
}
var entry = cachedEntries[index];
entry.SetValueFromSource(value);

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -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
{

View File

@ -21,6 +21,7 @@ namespace UnityExplorer.Core.Config
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;
@ -93,7 +94,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 +113,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)

View File

@ -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());
}

View File

@ -32,17 +32,33 @@ 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>();
: Il2CppSystem.Delegate.Combine(Camera.onPostRender,
(Camera.CameraCallback)new Action<Camera>(OnPostRender)).Cast<Camera.CameraCallback>();
if (Camera.onPostRender == null || Camera.onPostRender.delegates == null)
{
ExplorerCore.LogWarning("Failed to add Camera.onPostRender listener, falling back to LateUpdate instead!");
onPostRenderFailed = true;
}
#else
Camera.onPostRender += OnPostRender;
#endif
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception adding onPostRender listener: {ex.ReflectionExToString()}\r\nFalling back to LateUpdate!");
onPostRenderFailed = true;
}
}
internal void Update()
{
@ -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();
}

View File

@ -1,13 +1,13 @@
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;
using BF = System.Reflection.BindingFlags;
namespace UnityExplorer.Core.Input
{
@ -26,25 +26,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 +62,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 +79,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 +108,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 +119,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,14 +136,14 @@ 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;
}
}
@ -152,38 +153,31 @@ namespace UnityExplorer.Core.Input
{
try
{
if (CursorType == null)
throw new Exception("Could not load Type 'UnityEngine.Cursor'!");
// Get current cursor state and enable cursor
m_lastLockMode = (CursorLockMode?)CursorType.GetProperty("lockState", BF.Public | BF.Static)?.GetValue(null, null)
?? CursorLockMode.None;
m_lastVisibleState = (bool?)CursorType.GetProperty("visible", BF.Public | BF.Static)?.GetValue(null, null)
?? false;
ExplorerCore.Loader.SetupCursorPatches();
}
catch (Exception e)
{
ExplorerCore.Log($"Error on CursorUnlocker.Init! {e.GetType()}, {e.Message}");
ExplorerCore.Log($"Exception setting up Cursor patches: {e.GetType()}, {e.Message}");
}
}
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)
if (!UIManager.EventSys)
return;
if (!settingEventSystem && ShouldActuallyUnlock && !ConfigManager.Disable_EventSystem_Override.Value)
{
value = UIManager.EventSys;
value.enabled = true;
}
}
}
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
// Also keep track of when anything else tries to set Cursor state, this will be the
@ -191,9 +185,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 +196,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;

View File

@ -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
{

View File

@ -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);

View File

@ -99,8 +99,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;

View File

@ -15,6 +15,8 @@ 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 +26,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 +50,23 @@ namespace UnityExplorer
public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr)
{
if (cppClassPointers.TryGetValue(type.AssemblyQualifiedName, out il2cppPtr))
return il2cppPtr != IntPtr.Zero;
if (!cppClassPointers.TryGetValue(type.AssemblyQualifiedName, out il2cppPtr))
{
il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
.MakeGenericType(new Type[] { type })
.GetField("NativeClassPtr", BF.Public | BF.Static)
.GetValue(null);
cppClassPointers.Add(type.AssemblyQualifiedName, il2cppPtr);
}
return il2cppPtr != IntPtr.Zero;
}
#endregion
#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 +78,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)
{
DeobfuscatedTypes.Add(cppType.FullName, type);
reverseDeobCache.Add(type.FullName, cppType.FullName);
// Thanks to Slaynash for this
if (att.AttributeType == typeof(ObfuscatedNameAttribute))
{
string obfuscatedName = att.ConstructorArguments[0].Value.ToString();
DeobfuscatedTypes.Add(obfuscatedName, type);
reverseDeobCache.Add(type.FullName, obfuscatedName);
}
}
}
@ -103,7 +111,7 @@ namespace UnityExplorer
return theString;
}
#endregion
#endregion
// Get type by name
@ -116,7 +124,7 @@ namespace UnityExplorer
return base.Internal_GetTypeByName(fullName);
}
#region Get actual type
#region Get actual type
internal override Type Internal_GetActualType(object obj)
{
@ -124,7 +132,6 @@ namespace UnityExplorer
return null;
var type = obj.GetType();
try
{
if (IsString(obj))
@ -175,10 +182,10 @@ namespace UnityExplorer
return monoType;
}
#endregion
#endregion
#region Casting
#region Casting
private static readonly Dictionary<string, IntPtr> cppClassPointers = new Dictionary<string, IntPtr>();
@ -208,7 +215,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 +239,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 +265,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 +294,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 +403,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 +447,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,47 +472,32 @@ namespace UnityExplorer
base.Internal_FindSingleton(possibleNames, type, flags, instances);
}
#endregion
#endregion
#region Force-loading game modules
#region Force-loading game modules
internal static string UnhollowedFolderPath => Path.GetFullPath(
#if ML
Path.Combine("MelonLoader", "Managed")
#elif BIE
Path.Combine(BepInEx.Paths.BepInExRootPath, "unhollowed")
#else
Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Modules")
#endif
);
// Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded.
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))
if (Directory.Exists(UnhollowedFolderPath))
{
var files = Directory.GetFiles(dirpath);
var files = Directory.GetFiles(UnhollowedFolderPath);
foreach (var filePath in files)
{
var name = Path.GetFileName(filePath);
if (!name.StartsWith("Unity") && !name.StartsWith("Assembly-CSharp"))
continue;
try
{
DoLoadModule(filePath, true);
@ -484,6 +508,8 @@ namespace UnityExplorer
}
}
}
else
ExplorerCore.LogWarning($"Expected Unhollowed folder path does not exist: '{UnhollowedFolderPath}'");
}
internal bool DoLoadModule(string fullPath, bool suppressWarning = false)
@ -493,7 +519,8 @@ namespace UnityExplorer
try
{
Assembly.Load(File.ReadAllBytes(fullPath));
Assembly.LoadFile(fullPath);
//Assembly.Load(File.ReadAllBytes(fullPath));
return true;
}
catch (Exception e)
@ -510,7 +537,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.
@ -636,6 +663,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 +687,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 +799,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 +825,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 +879,7 @@ namespace UnityExplorer
try
{
var type = dictionary.GetType();
var type = dictionary.GetActualType();
if (typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(type))
{
@ -812,20 +889,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 +916,16 @@ namespace UnityExplorer
var keyEnumerator = getEnumeratorMethods[cacheKey].Invoke(keys, null);
var keyInfo = new EnumeratorInfo
{
current = keyEnumerator.GetType().GetProperty("Current"),
moveNext = keyEnumerator.GetType().GetMethod("MoveNext"),
current = keyEnumerator.GetActualType().GetProperty("Current"),
moveNext = keyEnumerator.GetActualType().GetMethod("MoveNext"),
};
var values = type.GetProperty("Values").GetValue(dictionary, null);
var valueEnumerator = values.GetType().GetMethod("GetEnumerator").Invoke(values, null);
var valueEnumerator = values.GetActualType().GetMethod("GetEnumerator").Invoke(values, null);
var valueInfo = new EnumeratorInfo
{
current = valueEnumerator.GetType().GetProperty("Current"),
moveNext = valueEnumerator.GetType().GetMethod("MoveNext"),
current = valueEnumerator.GetActualType().GetProperty("Current"),
moveNext = valueEnumerator.GetActualType().GetMethod("MoveNext"),
};
dictEnumerator = EnumerateCppDict(keyInfo, keyEnumerator, valueInfo, valueEnumerator);

View File

@ -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;
@ -43,7 +42,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 +65,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 +87,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 +101,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 +155,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 +219,7 @@ namespace UnityExplorer
return ret;
}
#endregion
#endregion
#region Type and Generic Parameter implementation cache
@ -355,7 +354,7 @@ namespace UnityExplorer
return genericParameterInheritance[key];
}
#endregion
#endregion
#region Internal MemberInfo Cache
@ -432,21 +431,17 @@ 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))
if (string.IsNullOrEmpty(blacklist) && !Instance.DefaultReflectionBlacklist.Any())
return;
try
{
var sigs = blacklist.Split(';');
foreach (var sig in sigs)
{
@ -456,9 +451,25 @@ namespace UnityExplorer
if (!currentBlacklist.Contains(s))
currentBlacklist.Add(s);
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception parsing blacklist string: {ex.ReflectionExToString()}");
}
foreach (var sig in Instance.DefaultReflectionBlacklist)
{
if (!currentBlacklist.Contains(sig))
currentBlacklist.Add(sig);
}
Mono.CSharp.IL2CPP.Blacklist.SignatureBlacklist = currentBlacklist;
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting up reflection blacklist: {ex.ReflectionExToString()}");
}
}
public static bool IsBlacklisted(MemberInfo member)
{
@ -466,6 +477,7 @@ namespace UnityExplorer
return false;
var sig = $"{member.DeclaringType.FullName}.{member.Name}";
return currentBlacklist.Contains(sig);
}
@ -496,6 +508,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 +569,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;
}
}
}

View File

@ -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)

View File

@ -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;
@ -14,8 +14,40 @@ using UnhollowerBaseLib;
namespace UnityExplorer.Tests
{
public class TestIndexer : IList<int>
{
private readonly List<int> list = new List<int>() { 1, 2, 3, 4, 5 };
public int Count => list.Count;
public bool IsReadOnly => false;
int IList<int>.this[int index]
{
get => list[index];
set => list[index] = value;
}
public int IndexOf(int item) => list.IndexOf(item);
public bool Contains(int item) => list.Contains(item);
public void Add(int item) => list.Add(item);
public void Insert(int index, int item) => list.Insert(index, item);
public bool Remove(int item) => list.Remove(item);
public void RemoveAt(int index) => list.RemoveAt(index);
public void Clear() => list.Clear();
public void CopyTo(int[] array, int arrayIndex) => list.CopyTo(array, arrayIndex);
public IEnumerator<int> GetEnumerator() => list.GetEnumerator();
IEnumerator IEnumerable.GetEnumerator() => list.GetEnumerator();
}
public static class TestClass
{
public static readonly TestIndexer AAAAATest = new TestIndexer();
public static void ATestMethod(string s, float f, Vector3 vector, DateTime date, Quaternion quater, bool b, CameraClearFlags enumvalue)
{
ExplorerCore.Log($"{s}, {f}, {vector.ToString()}, {date}, {quater.eulerAngles.ToString()}, {b}, {enumvalue}");

View File

@ -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,23 +131,13 @@ 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);
}
}
// Invoke the ToString method on the object
@ -157,9 +146,6 @@ 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);
}
catch (Exception ex)

View File

@ -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>>();
}
}
}

View File

@ -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,7 +20,7 @@ namespace UnityExplorer
public static class ExplorerCore
{
public const string NAME = "UnityExplorer";
public const string VERSION = "4.0.0";
public const string VERSION = "4.1.10";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer";
@ -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
}
}

View File

@ -2,19 +2,32 @@
<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>
<!-- MonoMod for MelonLoader 0.3.0 -->
<ItemGroup Condition="'$(IsMelonLoaderLegacy)'=='true'">
<InputAssemblies Include="packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.dll" />
<InputAssemblies Include="packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Mdb.dll" />
<InputAssemblies Include="packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Pdb.dll" />
<InputAssemblies Include="packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.Rocks.dll" />
<InputAssemblies Include="packages\MonoMod.RuntimeDetour.20.1.1.4\lib\net35\MonoMod.RuntimeDetour.dll" />
<InputAssemblies Include="packages\MonoMod.Utils.20.1.1.4\lib\net35\MonoMod.Utils.dll" />
</ItemGroup>
<!-- Required references for ILRepack -->
<ItemGroup>
<ReferenceFolders Include="..\lib\" />
<ReferenceFolders Include="..\lib\HarmonyX\Harmony\bin\Release\net35\" />
<ReferenceFolders Include="packages\HarmonyX.2.4.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 +35,8 @@
LibraryPath="@(ReferenceFolders)"
InputAssemblies="@(InputAssemblies)"
TargetKind="Dll"
OutputFile="$(OutputPath)$(AssemblyName).dll" />
OutputFile="$(OutputPath)$(AssemblyName).dll"
/>
</Target>
</Project>

View File

@ -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
{
int count = 0;
foreach (var comp in comps)
{
if (!comp)
continue;
count++;
if (!compInstanceIDs.Contains(comp.GetInstanceID()))
{
needRefresh = true;
break;
}
}
if (!needRefresh)
{
for (int i = 0; i < behaviours.Length; i++)
if (count != componentEntries.Count)
needRefresh = true;
else
{
var behaviour = behaviours[i];
if (behaviour.enabled != behaviourEnabledStates[i])
count = 0;
foreach (var behaviour in behaviours)
{
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)

View File

@ -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
{

View File

@ -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;

View File

@ -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);

View File

@ -11,7 +11,7 @@ using UnityExplorer.Core.Runtime;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
namespace UnityExplorer.UI.Inspectors
namespace UnityExplorer.Inspectors
{
public enum MouseInspectMode
{
@ -64,12 +64,15 @@ namespace UnityExplorer.UI.Inspectors
internal static Camera MainCamera;
internal static GraphicRaycaster[] graphicRaycasters;
public void StartInspect(MouseInspectMode mode)
{
MainCamera = Camera.main;
if (!MainCamera)
if (!MainCamera && mode == MouseInspectMode.World)
{
ExplorerCore.LogWarning("No MainCamera found! Cannot inspect world!");
return;
}
PanelDragger.ForceEnd();
@ -94,8 +97,14 @@ namespace UnityExplorer.UI.Inspectors
public void StopInspect()
{
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);
if (Mode == MouseInspectMode.UI)
@ -155,8 +164,8 @@ namespace UnityExplorer.UI.Inspectors
mousePos.x = 350;
if (mousePos.x > Screen.width - 350)
mousePos.x = Screen.width - 350;
if (mousePos.y < mainPanelRect.rect.height)
mousePos.y += mainPanelRect.rect.height + 10;
if (mousePos.y < Rect.rect.height)
mousePos.y += Rect.rect.height + 10;
else
mousePos.y -= 10;
@ -341,10 +350,10 @@ namespace UnityExplorer.UI.Inspectors
protected internal override void DoSetDefaultPosAndAnchors()
{
mainPanelRect.anchorMin = Vector2.zero;
mainPanelRect.anchorMax = Vector2.zero;
mainPanelRect.pivot = new Vector2(0.5f, 1);
mainPanelRect.sizeDelta = new Vector2(700, 150);
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()

View File

@ -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
{

View File

@ -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

View File

@ -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
{

View File

@ -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
{
@ -56,6 +55,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 +75,6 @@ namespace UnityExplorer.UI.Inspectors
private IEnumerator InitCoroutine()
{
yield return null;
LayoutRebuilder.ForceRebuildLayoutImmediate(InspectorPanel.Instance.ContentRect);
}
@ -309,11 +308,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)
@ -344,7 +340,7 @@ namespace UnityExplorer.UI.Inspectors
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 +350,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 +478,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 +615,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);
}

View File

@ -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) =>
{

View File

@ -7,12 +7,12 @@ 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;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Input;
using UnityExplorer.Loader.BIE;
#if CPP
using BepInEx.IL2CPP;
using UnhollowerRuntimeLib;
@ -46,7 +46,6 @@ namespace UnityExplorer
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;

View File

@ -10,7 +10,6 @@ namespace UnityExplorer
{
string ExplorerFolder { get; }
string ConfigFolder { get; }
ConfigHandler ConfigHandler { get; }
Action<object> OnLogMessage { get; }

View File

@ -9,11 +9,15 @@ using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Input;
using UnityExplorer.Loader.ML;
#if ML_LEGACY
using Harmony;
#else
using HarmonyLib;
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.UNIVERSAL)]
#endif
[assembly: MelonInfo(typeof(ExplorerMelonMod), ExplorerCore.NAME, ExplorerCore.VERSION, ExplorerCore.AUTHOR)]
[assembly: MelonGame(null, null)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.UNIVERSAL)]
[assembly: MelonColor(ConsoleColor.DarkCyan)]
namespace UnityExplorer
@ -23,7 +27,6 @@ namespace UnityExplorer
public static ExplorerMelonMod Instance;
public string ExplorerFolder => Path.Combine("Mods", ExplorerCore.NAME);
public string ConfigFolder => ExplorerFolder;
public ConfigHandler ConfigHandler => _configHandler;
public MelonLoaderConfigHandler _configHandler;
@ -67,7 +70,11 @@ namespace UnityExplorer
try
{
var prop = type.GetProperty(property);
#if ML_LEGACY
this.Harmony.Patch(prop.GetSetMethod(), prefix: prefix);
#else
HarmonyInstance.Patch(prop.GetSetMethod(), prefix: prefix);
#endif
}
catch (Exception e)
{

View File

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

View File

@ -76,8 +76,6 @@ namespace UnityExplorer
}
private static string s_explorerFolder;
public string ConfigFolder => ExplorerFolder;
Action<object> IExplorerLoader.OnLogMessage => (object log) => { OnLog?.Invoke(log?.ToString() ?? "", LogType.Log); };
Action<object> IExplorerLoader.OnLogWarning => (object log) => { OnLog?.Invoke(log?.ToString() ?? "", LogType.Warning); };
Action<object> IExplorerLoader.OnLogError => (object log) => { OnLog?.Invoke(log?.ToString() ?? "", LogType.Error); };

View File

@ -13,11 +13,11 @@ namespace UnityExplorer.Loader.STANDALONE
public class StandaloneConfigHandler : ConfigHandler
{
internal static IniDataParser _parser;
internal static string INI_PATH;
internal static string CONFIG_PATH;
public override void Init()
{
INI_PATH = Path.Combine(ExplorerCore.Loader.ConfigFolder, "config.ini");
CONFIG_PATH = Path.Combine(ExplorerCore.Loader.ExplorerFolder, "config.ini");
_parser = new IniDataParser();
_parser.Configuration.CommentString = "#";
}
@ -49,10 +49,10 @@ namespace UnityExplorer.Loader.STANDALONE
{
try
{
if (!File.Exists(INI_PATH))
if (!File.Exists(CONFIG_PATH))
return false;
string ini = File.ReadAllText(INI_PATH);
string ini = File.ReadAllText(CONFIG_PATH);
var data = _parser.Parse(ini);
@ -97,10 +97,10 @@ namespace UnityExplorer.Loader.STANDALONE
foreach (var entry in ConfigManager.ConfigElements)
sec.AddKey(entry.Key, entry.Value.BoxedValue.ToString());
if (!Directory.Exists(ExplorerCore.Loader.ConfigFolder))
Directory.CreateDirectory(ExplorerCore.Loader.ConfigFolder);
if (!Directory.Exists(ExplorerCore.Loader.ExplorerFolder))
Directory.CreateDirectory(ExplorerCore.Loader.ExplorerFolder);
File.WriteAllText(INI_PATH, data.ToString());
File.WriteAllText(CONFIG_PATH, data.ToString());
}
}
}

View File

@ -4,15 +4,13 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Inspectors;
using UnityExplorer.UI;
using UnityExplorer.UI.Models;
using UnityExplorer.UI.ObjectPool;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Utility;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete;
namespace UnityExplorer.UI.ObjectExplorer
namespace UnityExplorer.ObjectExplorer
{
public class ObjectSearch : UIModel
{
@ -55,8 +53,8 @@ namespace UnityExplorer.UI.ObjectExplorer
if (m_context == SearchContext.Singleton)
currentResults = SearchProvider.SingletonSearch(nameInputField.Text);
else if (m_context == SearchContext.StaticClass)
currentResults = SearchProvider.StaticClassSearch(nameInputField.Text);
else if (m_context == SearchContext.Class)
currentResults = SearchProvider.ClassSearch(nameInputField.Text);
else
{
string compType = "";
@ -79,7 +77,7 @@ namespace UnityExplorer.UI.ObjectExplorer
lastCheckedTypeInput = desiredTypeInput;
//var type = ReflectionUtility.GetTypeByName(desiredTypeInput);
if (ReflectionUtility.AllTypes.TryGetValue(desiredTypeInput, out var cachedType))
if (ReflectionUtility.GetTypeByName(desiredTypeInput) is Type cachedType)
{
var type = cachedType;
lastTypeCanHaveGO = typeof(Component).IsAssignableFrom(type) || type == typeof(GameObject);
@ -132,8 +130,11 @@ namespace UnityExplorer.UI.ObjectExplorer
if (!cachedCellTexts.ContainsKey(index))
{
string text;
if (m_context == SearchContext.StaticClass)
text = SignatureHighlighter.Parse(currentResults[index] as Type, true);
if (m_context == SearchContext.Class)
{
var type = currentResults[index] as Type;
text = $"{SignatureHighlighter.Parse(type, true)} <color=grey><i>({type.Assembly.GetName().Name})</i></color>";
}
else
text = ToStringUtility.ToStringWithType(currentResults[index], currentResults[index]?.GetActualType());
@ -145,7 +146,7 @@ namespace UnityExplorer.UI.ObjectExplorer
private void OnCellClicked(int dataIndex)
{
if (m_context == SearchContext.StaticClass)
if (m_context == SearchContext.Class)
InspectorManager.Inspect(currentResults[dataIndex] as Type);
else
InspectorManager.Inspect(currentResults[dataIndex]);

View File

@ -8,11 +8,12 @@ using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.UI;
using UnityExplorer.UI.Models;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
namespace UnityExplorer.UI.ObjectExplorer
namespace UnityExplorer.ObjectExplorer
{
public class SceneExplorer : UIModel
{
@ -39,7 +40,7 @@ namespace UnityExplorer.UI.ObjectExplorer
private GameObject refreshRow;
private Dropdown sceneDropdown;
private readonly Dictionary<int, Dropdown.OptionData> sceneToDropdownOption = new Dictionary<int, Dropdown.OptionData>();
private readonly Dictionary<Scene, Dropdown.OptionData> sceneToDropdownOption = new Dictionary<Scene, Dropdown.OptionData>();
private IEnumerable<GameObject> GetRootEntries() => SceneHandler.CurrentRootObjects;
@ -58,6 +59,26 @@ namespace UnityExplorer.UI.ObjectExplorer
Tree.RefreshData(true);
}
public void JumpToTransform(Transform transform)
{
if (!transform)
return;
UIManager.SetPanelActive(this.Parent, true);
this.Parent.SetTab(0);
// select the transform's scene
var go = transform.gameObject;
if (SceneHandler.SelectedScene != go.scene)
{
int idx = sceneDropdown.options.IndexOf(sceneToDropdownOption[go.scene]);
sceneDropdown.value = idx;
}
// Let the TransformTree handle the rest
Tree.JumpAndExpandToTransform(transform);
}
private void OnDropdownChanged(int value)
{
if (value < 0 || SceneHandler.LoadedScenes.Count <= value)
@ -71,12 +92,12 @@ namespace UnityExplorer.UI.ObjectExplorer
private void SceneHandler_OnInspectedSceneChanged(Scene scene)
{
if (!sceneToDropdownOption.ContainsKey(scene.handle))
if (!sceneToDropdownOption.ContainsKey(scene))
PopulateSceneDropdown();
if (sceneToDropdownOption.ContainsKey(scene.handle))
if (sceneToDropdownOption.ContainsKey(scene))
{
var opt = sceneToDropdownOption[scene.handle];
var opt = sceneToDropdownOption[scene];
int idx = sceneDropdown.options.IndexOf(opt);
if (sceneDropdown.value != idx)
sceneDropdown.value = idx;
@ -114,7 +135,7 @@ namespace UnityExplorer.UI.ObjectExplorer
var option = new Dropdown.OptionData(name);
sceneDropdown.options.Add(option);
sceneToDropdownOption.Add(scene.handle, option);
sceneToDropdownOption.Add(scene, option);
}
}
@ -122,7 +143,7 @@ namespace UnityExplorer.UI.ObjectExplorer
{
if ((!string.IsNullOrEmpty(input) && !Tree.Filtering) || (string.IsNullOrEmpty(input) && Tree.Filtering))
{
Tree.displayedObjects.Clear();
Tree.cachedTransforms.Clear();
}
Tree.CurrentFilter = input;

View File

@ -6,7 +6,7 @@ using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace UnityExplorer.Core
namespace UnityExplorer.ObjectExplorer
{
public static class SceneHandler
{
@ -15,16 +15,16 @@ namespace UnityExplorer.Core
/// </summary>
public static Scene? SelectedScene
{
get => m_selectedScene;
get => selectedScene;
internal set
{
if (m_selectedScene != null && m_selectedScene?.handle == value?.handle)
if (selectedScene != null && selectedScene == value)
return;
m_selectedScene = value;
OnInspectedSceneChanged?.Invoke((Scene)m_selectedScene);
selectedScene = value;
OnInspectedSceneChanged?.Invoke((Scene)selectedScene);
}
}
private static Scene? m_selectedScene;
private static Scene? selectedScene;
/// <summary>
/// The GameObjects in the currently inspected scene.
@ -37,6 +37,7 @@ namespace UnityExplorer.Core
/// </summary>
public static ReadOnlyCollection<Scene> LoadedScenes => new ReadOnlyCollection<Scene>(allLoadedScenes);
private static readonly List<Scene> allLoadedScenes = new List<Scene>();
private static HashSet<Scene> previousLoadedScenes;
/// <summary>
/// The names of all scenes in the build settings, if they could be retrieved.
@ -61,7 +62,7 @@ namespace UnityExplorer.Core
public static event Action<ReadOnlyCollection<Scene>> OnLoadedScenesChanged;
/// <summary>
/// Equivalent to <see cref="SceneManager.sceneCount"/> + 2, to include 'DontDestroyOnLoad'.
/// Equivalent to <see cref="SceneManager.sceneCount"/> + 2, to include 'DontDestroyOnLoad' and the 'None' scene.
/// </summary>
public static int LoadedSceneCount => SceneManager.sceneCount + 2;
@ -82,25 +83,7 @@ namespace UnityExplorer.Core
}
private static GameObject dontDestroyObject;
public static bool InspectingAssetScene => SelectedScene == AssetScene;
internal static Scene AssetScene => AssetObject.scene;
internal static int AssetHandle => AssetScene.handle;
internal static GameObject AssetObject
{
get
{
if (!assetObject)
{
assetObject = RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(GameObject))
.First(it => !it.TryCast<GameObject>().scene.IsValid())
.TryCast<GameObject>();
}
return assetObject;
}
}
private static GameObject assetObject;
public static bool InspectingAssetScene => SelectedScene.HasValue && SelectedScene.Value == default;
internal static void Init()
{
@ -122,46 +105,40 @@ namespace UnityExplorer.Core
catch (Exception ex)
{
gotAllScenesInBuild = false;
ExplorerCore.Log($"Unable to generate list of all Scenes in the build: {ex}");
ExplorerCore.LogWarning($"Unable to generate list of all Scenes in the build: {ex}");
}
}
internal static void Update()
{
int curHandle = SelectedScene?.handle ?? -1;
// DontDestroyOnLoad always exists, so default to true if our curHandle is that handle.
// otherwise we will check while iterating.
bool inspectedExists = curHandle == DontDestroyHandle || curHandle == AssetHandle;
// Quick sanity check if the loaded scenes changed
bool anyChange = LoadedSceneCount != allLoadedScenes.Count;
// otherwise keep a lookup table of the previous handles to check if the list changed at all.
HashSet<int> previousHandles = null;
if (!anyChange)
previousHandles = new HashSet<int>(allLoadedScenes.Select(it => it.handle));
// check if the loaded scenes changed. always confirm DontDestroy / HideAndDontSave
int confirmedCount = 2;
bool inspectedExists = SelectedScene == DontDestroyScene || (SelectedScene.HasValue && SelectedScene.Value == default);
allLoadedScenes.Clear();
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene scene = SceneManager.GetSceneAt(i);
if (scene == default || scene.handle == -1 || !scene.isLoaded)
if (scene == default || !scene.isLoaded)
continue;
// If no changes yet, ensure the previous list contained this handle.
if (!anyChange && !previousHandles.Contains(scene.handle))
anyChange = true;
// If no changes yet, ensure the previous list contained the scene
if (previousLoadedScenes != null && previousLoadedScenes.Contains(scene))
confirmedCount++;
// If we have not yet confirmed inspectedExists, check if this scene is our currently inspected one.
if (curHandle != -1 && !inspectedExists && scene.handle == curHandle)
if (!inspectedExists && scene == SelectedScene)
inspectedExists = true;
allLoadedScenes.Add(scene);
}
// Always add the DontDestroyOnLoad scene and the "none" scene.
bool anyChange = confirmedCount != allLoadedScenes.Count;
allLoadedScenes.Add(DontDestroyScene);
allLoadedScenes.Add(AssetScene);
allLoadedScenes.Add(default);
previousLoadedScenes = new HashSet<Scene>(allLoadedScenes);
// Default to first scene if none selected or previous selection no longer exists.
if (!inspectedExists)
@ -181,14 +158,14 @@ namespace UnityExplorer.Core
else
{
var allObjects = RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(GameObject));
var list = new List<GameObject>();
var objects = new List<GameObject>();
foreach (var obj in allObjects)
{
var go = obj.TryCast<GameObject>();
if (go.transform.parent == null && !go.scene.IsValid())
list.Add(go);
objects.Add(go);
}
rootObjects = list.ToArray();
rootObjects = objects.ToArray();
}
}
}

View File

@ -8,14 +8,13 @@ using UnityEngine.SceneManagement;
using UnityExplorer.Core;
using UnityExplorer.Core.Runtime;
namespace UnityExplorer.UI.ObjectExplorer
namespace UnityExplorer.ObjectExplorer
{
public enum SearchContext
{
UnityObject,
// GameObject,
Singleton,
StaticClass
Class
}
public enum ChildFilter
@ -45,9 +44,9 @@ namespace UnityExplorer.UI.ObjectExplorer
case SceneFilter.DontDestroyOnLoad:
return scene == SceneHandler.DontDestroyScene;
case SceneFilter.HideAndDontSave:
return scene == SceneHandler.AssetScene;
return scene == default;
case SceneFilter.ActivelyLoaded:
return scene != SceneHandler.DontDestroyScene && scene != SceneHandler.AssetScene;
return scene != SceneHandler.DontDestroyScene && scene != default;
default:
return false;
}
@ -58,25 +57,13 @@ namespace UnityExplorer.UI.ObjectExplorer
{
var results = new List<object>();
Type searchType;
switch (context)
{
//case SearchContext.GameObject:
// searchType = typeof(GameObject);
// break;
case SearchContext.UnityObject:
default:
Type searchType = null;
if (!string.IsNullOrEmpty(customTypeInput))
{
if (ReflectionUtility.GetTypeByName(customTypeInput) is Type customType)
{
if (typeof(UnityEngine.Object).IsAssignableFrom(customType))
{
searchType = customType;
break;
}
else
ExplorerCore.LogWarning($"Custom type '{customType.FullName}' is not assignable from UnityEngine.Object!");
}
@ -84,13 +71,8 @@ namespace UnityExplorer.UI.ObjectExplorer
ExplorerCore.LogWarning($"Could not find any type by name '{customTypeInput}'!");
}
searchType = typeof(UnityEngine.Object);
break;
}
if (searchType == null)
return results;
searchType = typeof(UnityEngine.Object);
var allObjects = RuntimeProvider.Instance.FindObjectsOfTypeAll(searchType);
@ -100,7 +82,7 @@ namespace UnityExplorer.UI.ObjectExplorer
if (!string.IsNullOrEmpty(input))
nameFilter = input;
bool canGetGameObject = searchType == typeof(GameObject) || typeof(Component).IsAssignableFrom(searchType);
bool shouldFilterGOs = searchType == typeof(GameObject) || typeof(Component).IsAssignableFrom(searchType);
foreach (var obj in allObjects)
{
@ -108,13 +90,21 @@ namespace UnityExplorer.UI.ObjectExplorer
if (!string.IsNullOrEmpty(nameFilter) && !obj.name.ContainsIgnoreCase(nameFilter))
continue;
if (canGetGameObject)
{
var go = searchType == typeof(GameObject)
? obj.TryCast<GameObject>()
: obj.TryCast<Component>().gameObject;
GameObject go = null;
var type = obj.GetActualType();
if (type == typeof(GameObject))
go = obj.TryCast<GameObject>();
else if (typeof(Component).IsAssignableFrom(type))
go = obj.TryCast<Component>()?.gameObject;
if (go)
{
// hide unityexplorer objects
if (go.transform.root.name == "ExplorerCanvas")
continue;
if (shouldFilterGOs)
{
// scene check
if (sceneFilter != SceneFilter.Any)
@ -143,7 +133,7 @@ namespace UnityExplorer.UI.ObjectExplorer
return results;
}
internal static List<object> StaticClassSearch(string input)
internal static List<object> ClassSearch(string input)
{
var list = new List<object>();
@ -153,11 +143,10 @@ namespace UnityExplorer.UI.ObjectExplorer
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.TryGetTypes().Where(it => it.IsSealed && it.IsAbstract))
foreach (var type in asm.TryGetTypes())
{
if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ContainsIgnoreCase(nameFilter))
continue;
list.Add(type);
}
}

Binary file not shown.

View File

@ -16,8 +16,11 @@ namespace UnityExplorer.UI
{
if (inputsPendingUpdate.Any())
{
foreach (var entry in inputsPendingUpdate)
var array = inputsPendingUpdate.ToArray();
for (int i = array.Length - 1; i >= 0; i--)
{
var entry = array[i];
LayoutRebuilder.MarkLayoutForRebuild(entry.Rect);
entry.OnValueChanged?.Invoke(entry.Component.text);
}
@ -47,8 +50,8 @@ namespace UnityExplorer.UI
}
public TextGenerator TextGenerator => Component.cachedInputTextGenerator;
public bool ReachedMaxVerts => TextGenerator.vertexCount >= UIManager.MAX_TEXT_VERTS;
public bool ReachedMaxVerts => TextGenerator.vertexCount >= UIManager.MAX_TEXT_VERTS;
private void OnInputChanged(string value)
{

View File

@ -1,19 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
namespace UnityExplorer.UI.ObjectPool
{
public interface IPooledObject
{
GameObject UIRoot { get; set; }
GameObject CreateContent(GameObject parent);
float DefaultHeight { get; }
//GameObject CreatePrototype();
}
}

View File

@ -1,4 +1,5 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
@ -6,8 +7,8 @@ using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UnityExplorer.UI.CSConsole;
using UnityExplorer.UI.Utility;
using UnityExplorer.CSConsole;
using UnityExplorer.UI.Widgets;
namespace UnityExplorer.UI.Panels
{
@ -18,10 +19,11 @@ namespace UnityExplorer.UI.Panels
public override int MinWidth => 750;
public override int MinHeight => 300;
public InputFieldScroller InputScroll { get; private set; }
public InputFieldRef Input => InputScroll.InputField;
public InputFieldScroller InputScroller { get; private set; }
public InputFieldRef Input => InputScroller.InputField;
public Text InputText { get; private set; }
public Text HighlightText { get; private set; }
public Text LineNumberText { get; private set; }
public Dropdown HelpDropdown { get; private set; }
@ -33,6 +35,7 @@ namespace UnityExplorer.UI.Panels
public Action<bool> OnCtrlRToggled;
public Action<bool> OnSuggestionsToggled;
public Action<bool> OnAutoIndentToggled;
public Action OnPanelResized;
private void InvokeOnValueChanged(string value)
{
@ -60,12 +63,17 @@ namespace UnityExplorer.UI.Panels
// UI Construction
public override void OnFinishResize(RectTransform panel)
{
OnPanelResized?.Invoke();
}
protected internal override void DoSetDefaultPosAndAnchors()
{
mainPanelRect.localPosition = Vector2.zero;
mainPanelRect.pivot = new Vector2(0f, 1f);
mainPanelRect.anchorMin = new Vector2(0.4f, 0.175f);
mainPanelRect.anchorMax = new Vector2(0.85f, 0.925f);
Rect.localPosition = Vector2.zero;
Rect.pivot = new Vector2(0f, 1f);
Rect.anchorMin = new Vector2(0.4f, 0.175f);
Rect.anchorMax = new Vector2(0.85f, 0.925f);
}
public override void ConstructPanelContent()
@ -121,19 +129,53 @@ namespace UnityExplorer.UI.Panels
// Console Input
var inputArea = UIFactory.CreateUIObject("InputGroup", content);
UIFactory.SetLayoutElement(inputArea, flexibleWidth: 9999, flexibleHeight: 9999);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(inputArea, false, true, true, true);
inputArea.AddComponent<Image>().color = Color.white;
inputArea.AddComponent<Mask>().showMaskGraphic = false;
// line numbers
var linesHolder = UIFactory.CreateUIObject("LinesHolder", inputArea);
var linesRect = linesHolder.GetComponent<RectTransform>();
linesRect.pivot = new Vector2(0, 1);
linesRect.anchorMin = new Vector2(0, 0);
linesRect.anchorMax = new Vector2(0, 1);
linesRect.sizeDelta = new Vector2(0, 305000);
linesRect.SetInsetAndSizeFromParentEdge(RectTransform.Edge.Left, 0, 50);
linesHolder.AddComponent<Image>().color = new Color(0.05f, 0.05f, 0.05f);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(linesHolder, true, true, true, true);
LineNumberText = UIFactory.CreateLabel(linesHolder, "LineNumbers", "1", TextAnchor.UpperCenter, Color.grey, fontSize: 16);
LineNumberText.font = UIManager.ConsoleFont;
// input field
int fontSize = 16;
var inputObj = UIFactory.CreateScrollInputField(this.content, "ConsoleInput", ConsoleController.STARTUP_TEXT, out var inputScroller, fontSize);
InputScroll = inputScroller;
var inputObj = UIFactory.CreateScrollInputField(inputArea, "ConsoleInput", ConsoleController.STARTUP_TEXT,
out var inputScroller, fontSize);
InputScroller = inputScroller;
ConsoleController.defaultInputFieldAlpha = Input.Component.selectionColor.a;
Input.OnValueChanged += InvokeOnValueChanged;
// move line number text with input field
linesRect.transform.SetParent(inputObj.transform.Find("Viewport"), false);
inputScroller.Slider.Scrollbar.onValueChanged.AddListener((float val) => { SetLinesPosition(); });
inputScroller.Slider.Slider.onValueChanged.AddListener((float val) => { SetLinesPosition(); });
void SetLinesPosition()
{
linesRect.anchoredPosition = new Vector2(linesRect.anchoredPosition.x, inputScroller.ContentRect.anchoredPosition.y);
//SetInputLayout();
}
InputText = Input.Component.textComponent;
InputText.supportRichText = false;
Input.PlaceholderText.fontSize = fontSize;
InputText.color = Color.clear;
Input.Component.customCaretColor = true;
Input.Component.caretColor = Color.white;
Input.PlaceholderText.fontSize = fontSize;
// Lexer highlight text overlay
var highlightTextObj = UIFactory.CreateUIObject("HighlightText", InputText.gameObject);
@ -154,7 +196,19 @@ namespace UnityExplorer.UI.Panels
Input.PlaceholderText.font = UIManager.ConsoleFont;
HighlightText.font = UIManager.ConsoleFont;
RuntimeProvider.Instance.StartCoroutine(DelayedLayoutSetup());
}
private IEnumerator DelayedLayoutSetup()
{
yield return null;
SetInputLayout();
}
public void SetInputLayout()
{
Input.Rect.offsetMin = new Vector2(52, Input.Rect.offsetMin.y);
Input.Rect.offsetMax = new Vector2(2, Input.Rect.offsetMax.y);
}
}
}

View File

@ -6,7 +6,7 @@ using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UnityExplorer.UI.Inspectors;
using UnityExplorer.Inspectors;
namespace UnityExplorer.UI.Panels
{
@ -26,8 +26,8 @@ namespace UnityExplorer.UI.Panels
public GameObject ContentHolder;
public RectTransform ContentRect;
public static float CurrentPanelWidth => Instance.mainPanelRect.rect.width;
public static float CurrentPanelHeight => Instance.mainPanelRect.rect.height;
public static float CurrentPanelWidth => Instance.Rect.rect.width;
public static float CurrentPanelHeight => Instance.Rect.rect.height;
public override void Update()
{
@ -38,7 +38,7 @@ namespace UnityExplorer.UI.Panels
{
base.OnFinishResize(panel);
InspectorManager.PanelWidth = this.mainPanelRect.rect.width;
InspectorManager.PanelWidth = this.Rect.rect.width;
InspectorManager.OnPanelResized(panel.rect.width);
}
@ -51,10 +51,10 @@ namespace UnityExplorer.UI.Panels
protected internal override void DoSetDefaultPosAndAnchors()
{
mainPanelRect.localPosition = Vector2.zero;
mainPanelRect.pivot = new Vector2(0f, 1f);
mainPanelRect.anchorMin = new Vector2(0.35f, 0.175f);
mainPanelRect.anchorMax = new Vector2(0.8f, 0.925f);
Rect.localPosition = Vector2.zero;
Rect.pivot = new Vector2(0f, 1f);
Rect.anchorMin = new Vector2(0.35f, 0.175f);
Rect.anchorMax = new Vector2(0.8f, 0.925f);
}
public override void ConstructPanelContent()

View File

@ -51,7 +51,7 @@ namespace UnityExplorer.UI.Panels
if (active && !DoneScrollPoolInit)
{
LayoutRebuilder.ForceRebuildLayoutImmediate(this.mainPanelRect);
LayoutRebuilder.ForceRebuildLayoutImmediate(this.Rect);
logScrollPool.Initialize(this);
DoneScrollPoolInit = true;
}
@ -158,10 +158,10 @@ namespace UnityExplorer.UI.Panels
protected internal override void DoSetDefaultPosAndAnchors()
{
mainPanelRect.localPosition = Vector2.zero;
mainPanelRect.pivot = new Vector2(0f, 1f);
mainPanelRect.anchorMin = new Vector2(0.5f, 0.03f);
mainPanelRect.anchorMax = new Vector2(0.9f, 0.2f);
Rect.localPosition = Vector2.zero;
Rect.pivot = new Vector2(0f, 1f);
Rect.anchorMin = new Vector2(0.5f, 0.03f);
Rect.anchorMax = new Vector2(0.9f, 0.2f);
}
// UI Construction

View File

@ -11,8 +11,7 @@ using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.UI.Models;
using UnityExplorer.UI.ObjectExplorer;
using UnityExplorer.UI.Utility;
using UnityExplorer.ObjectExplorer;
using UnityExplorer.UI.Widgets;
namespace UnityExplorer.UI.Panels
@ -99,10 +98,10 @@ namespace UnityExplorer.UI.Panels
protected internal override void DoSetDefaultPosAndAnchors()
{
mainPanelRect.localPosition = Vector2.zero;
mainPanelRect.pivot = new Vector2(0f, 1f);
mainPanelRect.anchorMin = new Vector2(0.125f, 0.175f);
mainPanelRect.anchorMax = new Vector2(0.325f, 0.925f);
Rect.localPosition = Vector2.zero;
Rect.pivot = new Vector2(0f, 1f);
Rect.anchorMin = new Vector2(0.125f, 0.175f);
Rect.anchorMax = new Vector2(0.325f, 0.925f);
//mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 350);
}

View File

@ -5,8 +5,8 @@ using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UnityExplorer.UI.CacheObject;
using UnityExplorer.UI.CacheObject.Views;
using UnityExplorer.CacheObject;
using UnityExplorer.CacheObject.Views;
using UnityExplorer.UI.Widgets;
namespace UnityExplorer.UI.Panels
@ -69,11 +69,11 @@ namespace UnityExplorer.UI.Panels
protected internal override void DoSetDefaultPosAndAnchors()
{
mainPanelRect.localPosition = Vector2.zero;
mainPanelRect.pivot = new Vector2(0f, 1f);
mainPanelRect.anchorMin = new Vector2(0.5f, 0.1f);
mainPanelRect.anchorMax = new Vector2(0.5f, 0.85f);
mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 600f);
Rect.localPosition = Vector2.zero;
Rect.pivot = new Vector2(0f, 1f);
Rect.anchorMin = new Vector2(0.5f, 0.1f);
Rect.anchorMax = new Vector2(0.5f, 0.85f);
Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 600f);
}
// UI Construction

View File

@ -1,12 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Input;
using System.IO;
using System.Diagnostics;
using UnityExplorer.UI.Models;
using System.Linq;
using UnityExplorer.UI.Widgets.AutoComplete;
namespace UnityExplorer.UI.Panels
@ -23,6 +23,7 @@ namespace UnityExplorer.UI.Panels
{
s_resizeCursorObj.SetActive(false);
wasAnyDragging = false;
Resizing = false;
foreach (var instance in Instances)
{
@ -250,15 +251,9 @@ namespace UnityExplorer.UI.Panels
Vector2 diff = (Vector2)mousePos - m_lastDragPosition;
m_lastDragPosition = mousePos;
var pos = Panel.localPosition + (Vector3)diff;
Panel.localPosition = Panel.localPosition + (Vector3)diff;
// Prevent panel going oustide screen bounds
var halfW = Screen.width * 0.5f;
var halfH = Screen.height * 0.5f;
pos.x = Math.Max(-halfW, Math.Min(pos.x, halfW - Panel.rect.width));
pos.y = Math.Max(-halfH + Panel.rect.height, Math.Min(pos.y, halfH));
Panel.localPosition = pos;
UIPanel.EnsureValidPosition(Panel);
}
public void OnEndDrag()
@ -425,6 +420,9 @@ namespace UnityExplorer.UI.Panels
if ((Vector2)mousePos == m_lastResizePos)
return;
if (mousePos.x < 0 || mousePos.y < 0 || mousePos.x > Screen.width || mousePos.y > Screen.height)
return;
m_lastResizePos = mousePos;
float diffX = (float)((decimal)diff.x / Screen.width);

View File

@ -8,7 +8,6 @@ using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Input;
using UnityExplorer.UI.Models;
using UnityExplorer.UI.Utility;
using UnityExplorer.UI.Widgets;
namespace UnityExplorer.UI.Panels
@ -44,8 +43,8 @@ namespace UnityExplorer.UI.Panels
continue;
// check if our mouse is clicking inside the panel
var pos = panel.mainPanelRect.InverseTransformPoint(mousePos);
if (!panel.Enabled || !panel.mainPanelRect.rect.Contains(pos))
var pos = panel.Rect.InverseTransformPoint(mousePos);
if (!panel.Enabled || !panel.Rect.rect.Contains(pos))
continue;
// if this is not the top panel, reorder and invoke the onchanged event
@ -88,9 +87,11 @@ namespace UnityExplorer.UI.Panels
public override GameObject UIRoot => uiRoot;
protected GameObject uiRoot;
protected RectTransform mainPanelRect;
public RectTransform Rect;
public GameObject content;
public GameObject titleBar;
public abstract void ConstructPanelContent();
public virtual void OnFinishResize(RectTransform panel)
@ -136,14 +137,84 @@ namespace UnityExplorer.UI.Panels
public void SetTransformDefaults()
{
DoSetDefaultPosAndAnchors();
if (mainPanelRect.rect.width < MinWidth)
mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth);
if (mainPanelRect.rect.height < MinHeight)
mainPanelRect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight);
}
public GameObject titleBar;
public void EnsureValidSize()
{
if (Rect.rect.width < MinWidth)
Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth);
if (Rect.rect.height < MinHeight)
Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight);
}
public static void EnsureValidPosition(RectTransform panel)
{
var pos = panel.localPosition;
// Prevent panel going oustide screen bounds
var halfW = Screen.width * 0.5f;
var halfH = Screen.height * 0.5f;
pos.x = Math.Max(-halfW - panel.rect.width + 50, Math.Min(pos.x, halfW - 50));
pos.y = Math.Max(-halfH + 50, Math.Min(pos.y, halfH));
panel.localPosition = pos;
}
#region Save Data
public abstract void DoSaveToConfigElement();
public void SaveToConfigManager()
{
if (UIManager.Initializing)
return;
DoSaveToConfigElement();
}
public abstract string GetSaveDataFromConfigManager();
public bool ApplyingSaveData { get; set; }
public virtual string ToSaveData()
{
try
{
return $"{ShouldSaveActiveState && Enabled}" +
$"|{Rect.RectAnchorsToString()}" +
$"|{Rect.RectPositionToString()}";
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception generating Panel save data: {ex}");
return "";
}
}
public virtual void ApplySaveData(string data)
{
if (string.IsNullOrEmpty(data))
return;
var split = data.Split('|');
try
{
Rect.SetAnchorsFromString(split[1]);
Rect.SetPositionFromString(split[2]);
UIManager.SetPanelActive(this.PanelType, bool.Parse(split[0]));
}
catch
{
ExplorerCore.LogWarning("Invalid or corrupt panel save data! Restoring to default.");
SetTransformDefaults();
}
}
#endregion
// UI Construction
public void ConstructUI()
{
@ -153,7 +224,7 @@ namespace UnityExplorer.UI.Panels
{
// create navbar button
NavButton = UIFactory.CreateButton(UIManager.NavbarButtonHolder, $"Button_{PanelType}", Name);
NavButton = UIFactory.CreateButton(UIManager.NavbarTabButtonHolder, $"Button_{PanelType}", Name);
var navBtn = NavButton.Component.gameObject;
navBtn.AddComponent<ContentSizeFitter>().horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(navBtn, false, true, true, true, 0, 0, 0, 5, 5, TextAnchor.MiddleCenter);
@ -168,7 +239,7 @@ namespace UnityExplorer.UI.Panels
// create core canvas
uiRoot = UIFactory.CreatePanel(Name, out GameObject panelContent);
mainPanelRect = this.uiRoot.GetComponent<RectTransform>();
Rect = this.uiRoot.GetComponent<RectTransform>();
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(this.uiRoot, false, false, true, true, 0, 2, 2, 2, 2, TextAnchor.UpperLeft);
int id = this.uiRoot.transform.GetInstanceID();
@ -177,9 +248,6 @@ namespace UnityExplorer.UI.Panels
content = panelContent;
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(this.content, false, false, true, true, 2, 2, 2, 2, 2, TextAnchor.UpperLeft);
// always apply default pos and anchors (save data may only be partial)
SetTransformDefaults();
// Title bar
titleBar = UIFactory.CreateHorizontalGroup(content, "TitleBar", false, true, true, true, 2,
new Vector4(2, 2, 2, 2), new Color(0.06f, 0.06f, 0.06f));
@ -210,7 +278,7 @@ namespace UnityExplorer.UI.Panels
// Panel dragger
Dragger = new PanelDragger(titleBar.GetComponent<RectTransform>(), mainPanelRect, this);
Dragger = new PanelDragger(titleBar.GetComponent<RectTransform>(), Rect, this);
Dragger.OnFinishResize += OnFinishResize;
Dragger.OnFinishDrag += OnFinishDrag;
@ -223,6 +291,7 @@ namespace UnityExplorer.UI.Panels
UIManager.SetPanelActive(this.PanelType, ShowByDefault);
ApplyingSaveData = true;
SetTransformDefaults();
// apply panel save data or revert to default
try
{
@ -234,6 +303,13 @@ namespace UnityExplorer.UI.Panels
SetTransformDefaults();
}
LayoutRebuilder.ForceRebuildLayoutImmediate(this.Rect);
// ensure initialized position is valid
EnsureValidSize();
EnsureValidPosition(this.Rect);
// update dragger and save data
Dragger.OnEndResize();
// simple listener for saving enabled state
@ -241,61 +317,11 @@ namespace UnityExplorer.UI.Panels
{
SaveToConfigManager();
};
ApplyingSaveData = false;
}
public override void ConstructUI(GameObject parent) => ConstructUI();
// SAVE DATA
public abstract void DoSaveToConfigElement();
public void SaveToConfigManager()
{
if (UIManager.Initializing)
return;
DoSaveToConfigElement();
}
public abstract string GetSaveDataFromConfigManager();
public bool ApplyingSaveData { get; set; }
public virtual string ToSaveData()
{
try
{
return $"{ShouldSaveActiveState && Enabled}" +
$"|{mainPanelRect.RectAnchorsToString()}" +
$"|{mainPanelRect.RectPositionToString()}";
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception generating Panel save data: {ex}");
return "";
}
}
public virtual void ApplySaveData(string data)
{
if (string.IsNullOrEmpty(data))
return;
var split = data.Split('|');
try
{
mainPanelRect.SetAnchorsFromString(split[1]);
mainPanelRect.SetPositionFromString(split[2]);
UIManager.SetPanelActive(this.PanelType, bool.Parse(split[0]));
}
catch
{
ExplorerCore.LogWarning("Invalid or corrupt panel save data! Restoring to default.");
SetTransformDefaults();
}
}
}
#region WINDOW ANCHORS / POSITION HELPERS

View File

@ -4,9 +4,16 @@ using System.Linq;
using System.Text;
using UnityEngine;
namespace UnityExplorer.UI.ObjectPool
namespace UnityExplorer.UI
{
// Abstract non-generic class, handles the pool dictionary and interfacing with the generic pools.
public interface IPooledObject
{
GameObject UIRoot { get; set; }
float DefaultHeight { get; }
GameObject CreateContent(GameObject parent);
}
public abstract class Pool
{
protected static readonly Dictionary<Type, Pool> pools = new Dictionary<Type, Pool>();
@ -39,7 +46,6 @@ namespace UnityExplorer.UI.ObjectPool
protected abstract void TryReturn(IPooledObject obj);
}
// Each generic implementation has its own pool, business logic is here
public class Pool<T> : Pool where T : IPooledObject
{
public static Pool<T> GetPool() => (Pool<T>)GetPool(typeof(T));

Some files were not shown because too many files have changed in this diff Show More