Compare commits

...

280 Commits
4.0.2 ... 4.5.6

Author SHA1 Message Date
d0bccae50c Update TypeCompleter.cs 2022-02-11 19:39:11 +11:00
bdf08f014f Use ReflectionUtility.AllTypes for type autocompleter, bump UniverseLib 2022-02-11 19:37:17 +11:00
df8522963e Use reflection for AllTypes until it's public 2022-02-07 22:12:18 +11:00
f6d0acab7b Bump version 2022-02-07 00:45:19 +11:00
d4fbc89158 Use ReflectionUtility.AllTypes when doing class search to include static classes 2022-02-06 04:50:52 +11:00
9e49f09a79 Bump UniverseLib, fix C# Console issues, add Stop helper 2022-02-04 20:34:05 +11:00
b9a3ab7439 Fix CSConsole not re-selecting properly after Escape is pressed 2022-02-04 18:07:34 +11:00
03661cdd0b Bump UniverseLib 2022-02-03 23:51:38 +11:00
10f2b7e849 Bump UniverseLib and fix changes to CreatePanel 2022-02-03 23:33:45 +11:00
4602f07c34 Add NuGet config 2022-02-01 18:20:01 +11:00
18bcfed434 Bump UniverseLib 2022-02-01 08:05:01 +11:00
c7d2ce6d36 Merge branch 'master' of https://github.com/sinai-dev/UnityExplorer 2022-01-31 22:01:35 +11:00
f961add7f4 Bump version 2022-01-31 22:00:59 +11:00
798b881dcb Update dotnet.yml 2022-01-31 21:31:18 +11:00
c90f339882 Update dotnet.yml 2022-01-31 21:29:10 +11:00
c351ce5a6d Remove Il2CppAssemblyUnhollower from build process 2022-01-31 21:24:57 +11:00
9985ed0fd8 Update to UniverseLib 1.2, cleanups 2022-01-31 21:24:01 +11:00
f35232bb64 Removed Unhollower submodule 2022-01-30 06:36:01 +11:00
49cc1f7df3 Fix issue with DontDestroyOnLoad not existing in some games, cleanups 2022-01-24 17:50:01 +11:00
9f78c50854 Use "untitled" for transforms with empty names 2022-01-24 17:49:49 +11:00
bc64a04339 Cleanup 2022-01-24 17:49:34 +11:00
7d961ce8ad Cleanup and DontDestroy fixes 2022-01-24 17:49:22 +11:00
c7d7569f71 Fix NullReferenceException in some older Unity games 2022-01-24 17:49:01 +11:00
ee8f33a754 Bump version 2022-01-24 01:10:19 +11:00
a95d91c824 Fix hidden UiBase not being deactivated with main one 2022-01-24 00:35:45 +11:00
177b78f4e7 Bump UniverseLib 2022-01-23 23:12:06 +11:00
3804d0aab0 Update ConfigManager.cs 2022-01-23 23:09:01 +11:00
97a82c1661 Make sure mouse is in target display for resize/dragging/panel operations 2022-01-23 23:08:34 +11:00
96a1cc69fb Bump UniverseLib 2022-01-23 17:44:01 +11:00
589abe6193 Fix panel draggers on display change 2022-01-22 21:44:22 +11:00
91bb58b020 Fix panel resizing on alternate monitors 2022-01-22 21:08:12 +11:00
d67507ead2 Implement DisplayManager, ability to use other monitors 2022-01-22 20:20:44 +11:00
d730fbe49c Update FUNDING.yml 2022-01-19 18:25:16 +11:00
9e0784ce54 Create FUNDING.yml 2022-01-19 18:24:49 +11:00
67c602b32f Use Tomlet for Standalone config, fix ML double reference 2022-01-19 18:20:26 +11:00
8fb7d87ca6 Use Tomlet, simplify panel data saving 2022-01-19 17:34:53 +11:00
c740c3c54d Update README.md 2022-01-18 20:34:15 +11:00
ea7b91f7fd Implement Clipboard and Notifications, misc cleanups 2022-01-18 20:19:20 +11:00
c79223f537 Move MouseInspector dropdown to Inspector panel 2022-01-17 21:00:55 +11:00
aae248bf69 Bump UniverseLib to 1.0.4, use HighlightMethod, add Clipboard (unfinished) 2022-01-17 20:23:58 +11:00
a73e992a0c Remove "Core" namespace, move everything in it up one level 2022-01-17 19:42:05 +11:00
3984550bb6 Bump Unhollower to latest 2022-01-17 19:41:00 +11:00
7928363aeb Bumped UniverseLib to 1.0.3 2022-01-11 05:54:38 +11:00
12758606fa Bump UniverseLib to 1.0.2 2022-01-09 19:56:59 +11:00
c927ac54be Fix potential NullReference in UpdateMouseInspect 2022-01-09 19:20:17 +11:00
9f1df11940 Add some more tests 2022-01-08 17:56:56 +11:00
62354b6aa2 Fix disposed TextWriter bug, add System.Collections to default using 2022-01-08 17:56:48 +11:00
44e57c32c3 Raise LangVersion to 9.0 2022-01-08 17:56:33 +11:00
274352d665 fix ParameterHandler parsing Types 2022-01-02 19:55:09 +11:00
7b477a8b0e Rewrite EvaluateWidget, add BaseArgumentHandler, use autocomplete for InteractiveEnum 2022-01-02 19:43:55 +11:00
e585fc6da0 Allow evaluating non-primitive methods 2022-01-01 21:40:47 +11:00
0dd19345ed Fix FPS drop with MelonLoader 2021-12-28 23:24:44 +11:00
0050cff185 bump version 2021-12-24 15:17:53 +11:00
5f1d635eb3 Only filter transform tree on end edit 2021-12-24 15:17:48 +11:00
e0f7fad00e Cleanup 2021-12-24 15:17:35 +11:00
f92c215a88 update libs 2021-12-24 15:17:28 +11:00
d58a5df82c Merge branch 'master' of https://github.com/sinai-dev/UnityExplorer 2021-12-24 15:17:23 +11:00
12dd85ecfd Update README.md 2021-12-03 01:06:57 +11:00
03fb165967 Update README (moved to UniverseLib) -noci 2021-12-02 21:11:25 +11:00
834f4b7c4c Update README.md 2021-12-02 20:27:51 +11:00
3415eb8e77 Bump version 2021-12-02 20:25:09 +11:00
6c1a5b8ae2 Update README.md 2021-12-02 18:36:33 +11:00
3334549902 use UniverseLib 2021-12-02 18:35:46 +11:00
077a2b434a Null ptr check on AssetBundle loading 2021-11-30 17:31:49 +11:00
8123ec2412 Revert "Use PatchProcessor ctor directly instead of Harmony.CreatePatchProcessor"
This reverts commit 9c1d459655.
2021-11-20 18:57:53 +11:00
3f31db8dd8 Merge branch 'master' of https://github.com/sinai-dev/UnityExplorer 2021-11-20 17:47:14 +11:00
9c1d459655 Use PatchProcessor ctor directly instead of Harmony.CreatePatchProcessor 2021-11-20 17:47:01 +11:00
2cb510a82e Cleanup readme
"Common issues" probably not that common anymore, removing to reduce clutter.
2021-11-18 05:12:04 +11:00
6aa9b3aa15 Bump version 2021-10-26 15:12:56 +11:00
587842f46b Add search filter to scene loader dropdown 2021-10-26 15:12:52 +11:00
b9c641f170 Fix scene loader not showing 2021-10-26 15:12:44 +11:00
1e68529430 Don't patch stripped UnloadAllAssetBundles 2021-10-22 15:54:14 +11:00
e84e96a63c Bump version 2021-10-21 20:04:48 +11:00
ec8c88ab9b Actually fix Logs folder 2021-10-19 16:34:46 +11:00
0eae78eeb2 Update LogPanel.cs 2021-10-19 16:32:33 +11:00
a07ead2142 Fix log folder not being created 2021-10-19 04:30:35 +11:00
b93567c7ea Bump version 2021-10-16 18:11:08 +11:00
957d80c7ec Fix IOUtility creating folders for file paths 2021-10-16 18:10:23 +11:00
d530d10798 Exceptions is IsNullOrDestroyed should return true 2021-10-12 20:27:32 +11:00
66daabf770 Add try/catch to IsNullOrDestroyed 2021-10-12 20:20:08 +11:00
9fe998aa22 Cleanup 2021-10-01 17:21:54 +10:00
28b6db80f9 Fix InputSystem EventSystem bug 2021-10-01 17:21:50 +10:00
22effbb399 Update README.md 2021-09-30 21:13:29 +10:00
50f0c31e98 Add patch for asset bundle unloading to prevent UnityExplorer's bundle being unloaded 2021-09-30 21:09:27 +10:00
911522457e Clarify "Classes" help in C# Console 2021-09-30 21:09:04 +10:00
d5d1841109 Include Unhollower in ILRepack references 2021-09-30 21:08:45 +10:00
239534e09c Add GetAllLoadedAssetBundles 2021-09-30 21:08:29 +10:00
bc5d16051f Fix issue with InputSystem 2021-09-30 21:08:08 +10:00
f81822f219 bump lib versions 2021-09-30 21:06:38 +10:00
f159cf5ea7 Attempt fix at bundle unloading 2021-09-23 16:40:47 +10:00
1e6bacb32b manual unity libs no longer required for BepInEx 2021-09-23 16:21:47 +10:00
a80cef4c1d Bump version 2021-09-07 19:11:58 +10:00
450bb77f2e Use Harmony's FullDescription for hook parameter types 2021-09-07 19:11:19 +10:00
0479102db6 Cleanup toggle Enabled logic 2021-09-07 18:06:38 +10:00
93181f02be Update README.md 2021-09-07 17:33:01 +10:00
371054d6df Add hook source editor for custom dynamic hooks 2021-09-07 17:23:20 +10:00
427f23b80a Bump version 2021-09-07 03:40:14 +10:00
a0d5ab8792 Fix ByRef parameters breaking generated patch code 2021-09-07 03:40:09 +10:00
297034e38b Cleanup 2021-09-07 03:19:40 +10:00
57f59d1295 Finalize hook manager update 2021-09-06 23:10:10 +10:00
fbdb84eefa Implement HookManager UI and logic 2021-09-06 23:04:40 +10:00
6989ea1b19 Misc cleanups 2021-09-06 23:03:55 +10:00
fcdfeb2dec Redesign mouse inspector class, add UI results panel 2021-09-06 17:10:01 +10:00
a1d0b6432e Cleanup 2021-09-06 15:51:40 +10:00
0b84405e57 Fix incorrect startup delay logic 2021-09-02 18:29:21 +10:00
c31e0949d3 Update ExplorerMelonMod.cs 2021-09-02 17:30:56 +10:00
5f0495a7ea Update README.md 2021-08-24 18:42:22 +10:00
28de6779d8 Fix for Canvas.renderingDisplaySize crash 2021-08-24 17:47:42 +10:00
fae85c2968 Bump version 2021-08-23 18:35:36 +10:00
fc8fa9aa7a Fix TestClass init mistake 2021-08-23 18:35:31 +10:00
bcf9a801a9 Fix suggestions still being given in comments sometimes 2021-08-23 18:35:22 +10:00
20298aa47b Make Type and Member names selectable / copy+paste-able 2021-08-23 18:35:08 +10:00
e2c1c186c3 Add wrapper to handle LemonAction issue 2021-08-23 18:31:26 +10:00
9ce6508828 Update README.md 2021-08-19 16:20:41 +10:00
506e75c5fe Handle Unhollowed modules path through IExplorerLoader. Allow Standalone release to specify manually. 2021-08-19 16:20:25 +10:00
a2f22051f0 Cleanup 2021-08-19 16:19:49 +10:00
4e3203a91b Make Assembly.GetTypes patch a finalizer instead of prefix 2021-08-10 17:31:12 +10:00
a411ce2dba Add patch for Assembly.GetTypes to catch exceptions 2021-08-10 17:17:44 +10:00
48132b3d46 Cleanup ILRepack 2021-08-10 17:17:21 +10:00
e49ed3028f Cleanup 2021-08-06 18:44:13 +10:00
31dd54d25c Reduce string alloc 2021-08-06 16:02:58 +10:00
3cfdd6fa43 Update readme -noci 2021-08-04 01:16:52 +10:00
1fa1283a68 Cleanup and update readme 2021-08-03 18:30:07 +10:00
afa4135b67 Update ML lib for "LemonCleanup" breaking changes 2021-08-03 16:12:28 +10:00
48d1cf574d Fix evaluate exception logic with methods that have arguments 2021-08-03 16:11:54 +10:00
df0abbc847 Small optimizations/cleanup 2021-08-03 16:11:16 +10:00
5c9dcb1d43 Bump HarmonyX to 2.5.2 2021-07-28 19:14:37 +10:00
602770d980 Cleanup 2021-07-28 19:01:43 +10:00
f26371f95f Go back to previous panel transform saving, no need to change 2021-07-28 18:27:10 +10:00
a673c39f4a Include arial in legacy bundles 2021-07-28 18:26:53 +10:00
40583cae3d Include arial in legacy bundles, cleanup 2021-07-28 06:41:02 +10:00
20018a9ba9 Use invariant decimal separator for NumberFormatString 2021-07-28 06:40:05 +10:00
dabf92a1a5 Use CurrentCulture, and use whitespace instead of comma as separator 2021-07-27 22:17:26 +10:00
b7e275f02c Cleanup 2021-07-27 22:12:48 +10:00
f393e0d706 Include Arial font, don't rely on built-in resource
TODO add font to legacy bundles
2021-07-27 18:14:44 +10:00
ab8acc9e84 Change directory case 2021-07-21 20:53:03 +10:00
66c30ee70e Cleanup 2021-07-21 20:48:21 +10:00
ddd271c00d Cleanup 2021-07-21 20:12:34 +10:00
30fe9e4dde Cleanup 2021-07-21 18:37:01 +10:00
fa3a436037 Cleanup 2021-07-21 18:29:01 +10:00
ca27d2b20f Update CursorUnlocker.cs 2021-07-21 05:18:22 +10:00
4315e0c547 Merge branch 'master' of https://github.com/sinai-dev/UnityExplorer 2021-07-21 04:23:55 +10:00
3e19e74329 Update readme -noci 2021-07-21 04:17:41 +10:00
5a3ffebadc Remove legacy build 2021-07-21 04:16:53 +10:00
6e6b6239d8 Do patching in CursorUnlocker, add patch for SetSelectedGameObject 2021-07-21 04:14:34 +10:00
03c8e5a8bd Add Harmony instance to ExplorerCore, bump version 2021-07-21 04:14:12 +10:00
44f7209843 Remove ML 0.3 builds 2021-07-21 04:13:54 +10:00
734e45cf9f Remove patching in mod loader classes 2021-07-21 04:13:42 +10:00
97838e0b3a Update Unhollower 2021-07-21 04:13:15 +10:00
304b47f898 Bump version 2021-07-19 21:43:40 +10:00
3d1fcbcd9f Fix duplicate Scene hashcodes breaking PopulateSceneDropdown 2021-07-19 21:43:36 +10:00
36fc17aa43 Get rid of pointless ReadOnlyCollection properties 2021-07-19 21:43:16 +10:00
adfa29e63c Bump version 2021-07-15 05:20:31 +10:00
3ffdcea73b Force all devices to always be supported by InputSystem 2021-07-15 05:20:26 +10:00
dfd55260a8 Fallback to null check loading if unityVersion throws an exception 2021-07-15 05:19:48 +10:00
29c78dc5a6 Manually get Major and Minor instead of using Version class 2021-07-11 23:22:33 +10:00
bf59d9d6cd Handle cases where Unity doesn't include 'f' suffix on version
Not sure if this ever happens but just in case
2021-07-11 18:19:09 +10:00
bb0c59534a Bump version 2021-07-11 18:11:18 +10:00
802bb722bc Use Application.unityVersion to load appropriate AssetBundle
And fix issue with Dropdowns and Time input label not refreshing properly on launch
2021-07-11 18:11:14 +10:00
9ca992b0d7 Fix incorrect config description example 2021-07-07 23:27:43 +10:00
5f3b3a6870 cleanup -noci 2021-07-07 23:21:40 +10:00
f5bce439cb Favor public GetEnumerator over private. Use GetActualType. Improve logging. 2021-07-06 16:42:20 +10:00
e2b2c9038a Add 'other' issue template -noci 2021-07-05 19:05:04 +10:00
fbefccd6b7 Update issue template -noci 2021-07-02 23:32:02 +10:00
271c91f0d0 Update README.md 2021-07-02 20:03:41 +10:00
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
177 changed files with 5716 additions and 10612 deletions

3
.github/FUNDING.yml vendored Normal file
View File

@ -0,0 +1,3 @@
# These are supported funding model platforms
ko_fi: sinaidev

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

@ -0,0 +1,60 @@
name: Bug Report
description: File a bug or crash report
title: "[Bug]: "
labels: [bug]
body:
- type: markdown
attributes:
value: |
Thanks for submitting a bug report, please fill out as much detail as possible.
- type: checkboxes
id: latestversion
attributes:
label: Are you on the latest version of UnityExplorer?
description: If not, you must update first.
options:
- label: Yes, I'm on the latest version of UnityExplorer.
required: true
- type: dropdown
id: version
attributes:
label: Which release are you using?
description: Please select your environment for UnityExplorer.
options:
- BepInEx IL2CPP
- BepInEx 6.X Mono
- BepInEx 5.X Mono
- MelonLoader IL2CPP
- MelonLoader Mono
- Standalone IL2CPP
- Standalone Mono
validations:
required: true
- type: textarea
id: game
attributes:
label: Which game did this occur on?
description: Please tell us the name of the game. If it's a personal or private project, just let us know the Unity version.
validations:
required: true
- type: textarea
id: what-happened
attributes:
label: Describe the issue.
description: What happened? Should something else have happened instead? Please provide steps to reproduce the issue if possible.
placeholder: Tell us what you see!
validations:
required: true
- type: textarea
id: logs
attributes:
label: Relevant log output
description: |
Please copy and paste any relevant logs and stack traces.
* Unity log: `%userprofile%\AppData\LocalLow\{Company}\{Game}\Player.log` or `output_log.txt`
* BepInEx: `BepInEx\LogOutput.log`
* MelonLoader: `MelonLoader\latest.log`
* Standalone: `{DLL_Location}\UnityExplorer\Logs\` (pick the most recent one)
render: shell
validations:
required: false

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
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

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

@ -0,0 +1,95 @@
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 mcs
- run: nuget restore lib\mcs-unity\mcs.sln
- run: msbuild lib\mcs-unity\mcs\mcs.csproj -t:Restore -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release
# Build UnityExplorer releases, and upload artifacts
- run: dotnet nuget add source https://nuget.bepinex.dev/v3/index.json
- run: nuget restore src\UnityExplorer.sln
# BepInEx Il2Cpp
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_BIE_Cpp
- uses: actions/upload-artifact@v2
with:
name: UnityExplorer.BepInEx.Il2Cpp
path: ./Release/UnityExplorer.BepInEx.Il2Cpp/*
# BepInEx 5 Mono
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_BIE5_Mono
- uses: actions/upload-artifact@v2
with:
name: UnityExplorer.BepInEx5.Mono
path: ./Release/UnityExplorer.BepInEx5.Mono/*
# BepInEx 6 Mono
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_BIE6_Mono
- uses: actions/upload-artifact@v2
with:
name: UnityExplorer.BepInEx6.Mono
path: ./Release/UnityExplorer.BepInEx6.Mono/*
# MelonLoader Il2Cpp
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_ML_Cpp
- uses: actions/upload-artifact@v2
with:
name: UnityExplorer.MelonLoader.Il2Cpp
path: ./Release/UnityExplorer.MelonLoader.Il2Cpp/*
# MelonLoader Mono
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_ML_Mono
- uses: actions/upload-artifact@v2
with:
name: UnityExplorer.MelonLoader.Mono
path: ./Release/UnityExplorer.MelonLoader.Mono/*
# Standalone Il2Cpp
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_STANDALONE_Cpp
- uses: actions/upload-artifact@v2
with:
name: UnityExplorer.Standalone.Il2Cpp
path: ./Release/UnityExplorer.Standalone.Il2Cpp/*
# Standalone Mono
- run: msbuild src\UnityExplorer.csproj -t:Rebuild -p:Platform="AnyCPU" -p:Configuration=Release_STANDALONE_Mono
- uses: actions/upload-artifact@v2
with:
name: UnityExplorer.Standalone.Mono
path: ./Release/UnityExplorer.Standalone.Mono/*

6
.gitmodules vendored
View File

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

147
README.md
View File

@ -3,53 +3,70 @@
</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">
✨ Powered by <a href="https://github.com/sinai-dev/UniverseLib">UniverseLib</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
* Any `MissingMethodException` or `NotSupportedException`: please report the issue and provide a copy of your mod loader log and/or Unity log.
* The C# console may unexpectedly produce a GC Mark Overflow crash when calling certain outside methods. Not clear yet what is causing this, but it's being looked into.
* In IL2CPP, some IEnumerable and IDictionary types may fail enumeration. Waiting for the Unhollower rewrite to address this any further.
* In IL2CPP, the C# console might not suggest deobfuscated (or obfuscated) names. Being looked into.
⚡ Thunderstore releases: [BepInEx Mono](https://thunderstore.io/package/sinai-dev/UnityExplorer) | [BepInEx IL2CPP](https://gtfo.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP) | [MelonLoader IL2CPP](https://boneworks.thunderstore.io/package/sinai-dev/UnityExplorer_IL2CPP_ML)
## How to install
## BepInEx
### BepInEx
| Release | IL2CPP | Mono |
| ------- | ------ | ---- |
| BIE 6.X | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx6.Mono.zip) |
| BIE 5.X | ✖️ n/a | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx5.Mono.zip) |
1. Install [BepInEx](https://github.com/BepInEx/BepInEx) for your game. IL2CPP currently requires a [Bleeding Edge](https://builds.bepis.io/projects/bepinex_be) release.
2. Download the UnityExplorer release for BepInEx IL2CPP or Mono above.
3. Take the `UnityExplorer.BIE.___.dll` file and put it in `[GameFolder]\BepInEx\plugins\`
4. In IL2CPP, you will need to download the [Unity libs](https://github.com/LavaGang/Unity-Runtime-Libraries) for the game's Unity version and put them in the `BepInEx\unity-libs\` folder.
1. Take the `UnityExplorer.BIE.[version].dll` and the `UniverseLib.[version].dll` files and put them in `BepInEx\plugins\`
### MelonLoader
<i>Note: BepInEx 6 is obtainable via [BepisBuilds](https://builds.bepis.io/projects/bepinex_be)</i>
1. Install [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) 0.3.1+ for your game. This version can currently be obtained from [here](https://github.com/LavaGang/MelonLoader/actions).
2. Download the UnityExplorer release for MelonLoader IL2CPP or Mono above.
3. Take the `UnityExplorer.ML.___.dll` file and put it in the `[GameFolder]\Mods\` folder.
## MelonLoader
### Standalone
| Release | IL2CPP | Mono |
| ------- | ------ | ---- |
| ML 0.4+ | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Mono.zip) |
The standalone release can be used with any injector or loader of your choice, but it requires you to load the dependencies manually: HarmonyX, and the IL2CPP version also requires that you set up an [Il2CppAssemblyUnhollower runtime](https://github.com/knah/Il2CppAssemblyUnhollower#required-external-setup).
1. Take the `UnityExplorer.ML.[version].dll` file and put them in the `Mods\` folder created by MelonLoader, and then put the `UniverseLib.[version].dll` file in the `UserLibs\` folder.
1. Load the required libs - HarmonyX, and Il2CppAssemblyUnhollower if IL2CPP
## 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.
1. Ensure the required libs are loaded - UniverseLib, HarmonyX and MonoMod
2. For IL2CPP, load Il2CppAssemblyUnhollower and start an [Il2CppAssemblyUnhollower runtime](https://github.com/knah/Il2CppAssemblyUnhollower#required-external-setup)
2. Load the UnityExplorer DLL
3. Create an instance of Unity Explorer with `UnityExplorer.ExplorerStandalone.CreateInstance();`
4. Optionally subscribe to the `ExplorerStandalone.OnLog` event to handle logging if you wish
## Features
# Common issues and solutions
Although UnityExplorer should work out of the box for most Unity games, in some cases you may need to tweak the settings for it to work properly.
To adjust the settings, open the config file:
* BepInEx: `BepInEx\config\com.sinai.unityexplorer.cfg`
* MelonLoader: `UserData\MelonPreferences.cfg`
* Standalone: `UnityExplorer\config.cfg`
Try adjusting the following settings and see if it fixes your issues:
* `Startup_Delay_Time` - increase to 5-10 seconds (or more as needed), can fix issues with UnityExplorer being destroyed or corrupted during startup.
* `Disable_EventSystem_Override` - if input is not working properly, try setting this to `true`.
If these fixes do not work, please create an issue in this repo and I'll do my best to look into it.
# Features
<p align="center">
<a href="https://raw.githubusercontent.com/sinai-dev/UnityExplorer/master/img/preview.png">
@ -59,52 +76,76 @@ The standalone release can be used with any injector or loader of your choice, b
### Object Explorer
* Use the <b>Scene Explorer</b> tab to traverse the active scenes, as well as the DontDestroyOnLoad scene and the HideAndDontSave "scene" (assets and hidden objects).
* Use the <b>Scene Explorer</b> tab to traverse the active scenes, as well as the DontDestroyOnLoad and HideAndDontSave objects.
* The "HideAndDontSave" scene contains objects with that flag, as well as Assets and Resources which are not in any scene but behave the same way.
* You can use the Scene Loader to easily load any of the scenes in the build (may not work for Unity 5.X games)
* Use the <b>Object Search</b> tab to search for Unity objects (including GameObjects, Components, etc), C# Singletons or Static Classes.
* Use the UnityObject search to look for any objects which derive from `UnityEngine.Object`, with optional filters
* The singleton search will look for any classes with a typical "Instance" field, and check it for a current value. This may cause unexpected behaviour in some IL2CPP games as we cannot distinguish between true properties and field-properties, so some property accessors will be invoked.
### Inspector
The inspector is used to see detailed information on GameObjects (GameObject Inspector), C# objects (Reflection Inspector) and C# classes (Static Inspector).
The inspector is used to see detailed information on objects of any type and manipulate their values, as well as to inspect C# Classes with static reflection.
For the GameObject Inspector, you can edit any of the input fields in the inspector (excluding readonly fields) and press <b>Enter</b> to apply your changes. You can also do this to the GameObject path as a way to change the GameObject's parent. Press the <b>Escape</b> key to cancel your edits.
In the Reflection Inspectors, automatic updating is not enabled by default, and you must press Apply for any changes you make to take effect.
* The <b>GameObject Inspector</b> (tab prefix `[G]`) is used to inspect a `GameObject`, and to see and manipulate its Transform and Components.
* You can edit any of the input fields in the inspector (excluding readonly fields) and press <b>Enter</b> to apply your changes. You can also do this to the GameObject path as a way to change the GameObject's parent. Press the <b>Escape</b> key to cancel your edits.
* <i>note: When inspecting a GameObject with a Canvas, the transform controls may be overridden by the RectTransform anchors.</i>
* The <b>Reflection Inspectors</b> (tab prefix `[R]` and `[S]`) are used for everything else
* Automatic updating is not enabled by default, and you must press Apply for any changes you make to take effect.
* Press the `▼` button to expand certain values such as strings, enums, lists, dictionaries, some structs, etc
* Use the filters at the top to quickly find the members you are looking for
* For `Texture2D` objects, there is a `View Texture` button at the top of the inspector which lets you view it and save it as a PNG file. Currently there are no other similar helpers yet, but I may add more at some point for Mesh, Sprite, Material, etc
### C# Console
The C# Console uses the `Mono.CSharp.Evaluator` to define temporary classes or run immediate REPL code.
* The C# Console uses the `Mono.CSharp.Evaluator` to define temporary classes or run immediate REPL code.
* You can execute a script automatically on startup by naming it `startup.cs` and placing it in the `UnityExplorer\Scripts\` folder (this folder will be created where you placed the DLL file).
* See the "Help" dropdown in the C# console menu for more detailed information.
See the "Help" dropdown in the C# console menu for more detailed information.
### Hook Manager
* The Hooks panel allows you to hook methods at the click of a button for debugging purposes.
* Simply enter any class (generic types not yet supported) and hook the methods you want from the menu.
* You can edit the source code of the generated hook with the "Edit Hook Source" button. Accepted method names are `Prefix` (which can return `bool` or `void`), `Postfix`, `Finalizer` (which can return `Exception` or `void`), and `Transpiler` (which must return `IEnumerable<HarmonyLib.CodeInstruction>`). You can define multiple patches if you wish.
### Mouse-Inspect
The "Mouse Inspect" dropdown on the main UnityExplorer navbar allows you to inspect objects under the mouse.
* The "Mouse Inspect" dropdown in the "Inspector" panel allows you to inspect objects under the mouse.
* <b>World</b>: uses Physics.Raycast to look for Colliders
* <b>UI</b>: uses GraphicRaycasters to find UI objects
* <b>World</b>: uses Physics.Raycast to look for Colliders
* <b>UI</b>: uses GraphicRaycasters to find UI objects
### Clipboard
* The "Clipboard" panel allows you to see your current paste value, or clear it (resets it to `null`)
* Can copy the value from any member in a Reflection Inspector, Enumerable or Dictionary, and from the target of any Inspector tab
* Can paste values onto any member in a Reflection Inspector
* Non-parsable arguments in Method/Property Evaluators allow pasting values
* The C# Console has helper methods `Copy(obj)` and `Paste()` for accessing the Clipboard
### Settings
You can change the settings via the "Options" 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.cfg`
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
0. Clone the repository and run `git submodule update --init --recursive` to get the submodules.
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.
1. Open the `src\UnityExplorer.sln` project.
2. Build `mcs` (Release/AnyCPU, you may need to run `nuget restore mcs.sln`)
3. Build the UnityExplorer release(s) you want to use, either by selecting the config as the Active Config, or batch-building.
## Acknowledgments
If you fork the repository on GitHub you can build using the [dotnet workflow](https://github.com/sinai-dev/UnityExplorer/blob/master/.github/workflows/dotnet.yml):
0. Click on the Actions tab and enable workflows in your repository
1. Click on the "Build UnityExplorer" workflow, then click "Run Workflow" and run it manually, or make a new commit to trigger the workflow.
2. Take the artifact from the completed run.
# Acknowledgments
* [ManlyMarco](https://github.com/ManlyMarco) for [Runtime Unity Editor](https://github.com/ManlyMarco/RuntimeUnityEditor) \[[license](THIRDPARTY_LICENSES.md#runtimeunityeditor-license)\], the ScriptEvaluator from RUE's REPL console was used as the base for UnityExplorer's C# console.
* [denikson](https://github.com/denikson) (aka Horse) for [mcs-unity](https://github.com/denikson/mcs-unity) \[no license\], used as the `Mono.CSharp` reference for the C# Console.
* [HerpDerpenstine](https://github.com/HerpDerpinstine) for [MelonCoroutines](https://github.com/LavaGang/MelonLoader/blob/6cc958ec23b5e2e8453a73bc2e0d5aa353d4f0d1/MelonLoader.Support.Il2Cpp/MelonCoroutines.cs) \[[license](THIRDPARTY_LICENSES.md#melonloader-license)\], they were included for standalone IL2CPP coroutine support.
* [Geoffrey Horsington](https://github.com/ghorsington) for [mcs-unity](https://github.com/sinai-dev/mcs-unity) \[no license\], used as the `Mono.CSharp` reference for the C# Console.
### Disclaimer

Binary file not shown.

Binary file not shown.

Binary file not shown.

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.

Binary file not shown.

View File

@ -3,10 +3,15 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityExplorer.UI.CSConsole.Lexers;
using UnityExplorer.CSConsole.Lexers;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.Utility;
namespace UnityExplorer.UI.CSConsole
namespace UnityExplorer.CSConsole
{
public class CSAutoCompleter : ISuggestionProvider
{
@ -22,8 +27,7 @@ namespace UnityExplorer.UI.CSConsole
AutoCompleteModal.Instance.ReleaseOwnership(this);
}
// Delimiters for completions, notably does not include '.'
private readonly HashSet<char> delimiters = new HashSet<char>
private readonly HashSet<char> delimiters = new()
{
'{', '}', ',', ';', '<', '>', '(', ')', '[', ']', '=', '|', '&', '?'
};
@ -41,7 +45,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,22 +59,22 @@ 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
string[] evaluatorCompletions = ConsoleController.Evaluator.GetCompletions(input, out string prefix);
if (evaluatorCompletions != null && evaluatorCompletions.Any())
{
suggestions.AddRange(from completion in evaluatorCompletions
@ -99,7 +103,7 @@ namespace UnityExplorer.UI.CSConsole
{
if (!keywordHighlights.ContainsKey(kw))
keywordHighlights.Add(kw, $"<color=#{SignatureHighlighter.keywordBlueHex}>{kw}</color>");
string completion = kw.Substring(input.Length, kw.Length - input.Length);
suggestions.Add(new Suggestion(keywordHighlights[kw], completion));
}
@ -123,7 +127,7 @@ namespace UnityExplorer.UI.CSConsole
private readonly StringBuilder highlightBuilder = new StringBuilder();
private const string OPEN_HIGHLIGHT = "<color=cyan>";
private string GetHighlightString(string prefix, string completion)
{
highlightBuilder.Clear();

View File

@ -1,18 +1,27 @@
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 UniverseLib.Input;
using UnityExplorer.CSConsole;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib.UI;
using UniverseLib;
using UniverseLib.UI.Models;
using UniverseLib.Utility;
using HarmonyLib;
using UniverseLib.Runtime;
namespace UnityExplorer.UI.CSConsole
namespace UnityExplorer.CSConsole
{
public static class ConsoleController
{
@ -22,6 +31,7 @@ namespace UnityExplorer.UI.CSConsole
private static HashSet<string> usingDirectives;
private static StringBuilder evaluatorOutput;
private static StringWriter evaluatorStringWriter;
public static CSConsolePanel Panel => UIManager.GetPanel<CSConsolePanel>(UIManager.Panels.CSConsole);
public static InputFieldRef Input => Panel.Input;
@ -34,11 +44,14 @@ 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",
"System.Linq",
"System.Text",
"System.Collections",
"System.Collections.Generic",
"UnityEngine",
#if CPP
@ -49,6 +62,9 @@ namespace UnityExplorer.UI.CSConsole
public static void Init()
{
InitEventSystemPropertyHandlers();
// Make sure console is supported on this platform
try
{
ResetConsole(false);
@ -61,21 +77,42 @@ 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}");
}
}
#region UI Listeners and options
@ -101,6 +138,12 @@ namespace UnityExplorer.UI.CSConsole
#region Evaluating
private static void GenerateTextWriter()
{
evaluatorOutput = new StringBuilder();
evaluatorStringWriter = new StringWriter(evaluatorOutput);
}
public static void ResetConsole() => ResetConsole(true);
public static void ResetConsole(bool logSuccess = true)
@ -111,8 +154,8 @@ namespace UnityExplorer.UI.CSConsole
if (Evaluator != null)
Evaluator.Dispose();
evaluatorOutput = new StringBuilder();
Evaluator = new ScriptEvaluator(new StringWriter(evaluatorOutput))
GenerateTextWriter();
Evaluator = new ScriptEvaluator(evaluatorStringWriter)
{
InteractiveBaseClass = typeof(ScriptInteraction)
};
@ -147,17 +190,24 @@ namespace UnityExplorer.UI.CSConsole
if (SRENotSupported)
return;
if (evaluatorStringWriter == null || evaluatorOutput == null)
{
GenerateTextWriter();
Evaluator._textWriter = evaluatorStringWriter;
}
try
{
// Try to "Compile" the code (tries to interpret it as REPL)
var evaluation = Evaluator.Compile(input);
if (evaluation != null)
// Compile the code. If it returned a CompiledMethod, it is REPL.
CompiledMethod repl = Evaluator.Compile(input);
if (repl != null)
{
// Valid REPL, we have a delegate to the evaluation.
try
{
object ret = null;
evaluation.Invoke(ref ret);
repl.Invoke(ref ret);
var result = ret?.ToString();
if (!string.IsNullOrEmpty(result))
ExplorerCore.Log($"Invoked REPL, result: {ret}");
@ -171,11 +221,9 @@ namespace UnityExplorer.UI.CSConsole
}
else
{
// The input was not recognized as an evaluation. Compile the code.
// The compiled code was not REPL, so it was a using directive or it defined classes.
Evaluator.Run(input);
string output = ScriptEvaluator._textWriter.ToString();
string output = Evaluator._textWriter.ToString();
var outputSplit = output.Split('\n');
if (outputSplit.Length >= 2)
output = outputSplit[outputSplit.Length - 2];
@ -220,7 +268,7 @@ namespace UnityExplorer.UI.CSConsole
if (InputManager.GetKeyDown(KeyCode.Escape))
{
Input.Text = previousInput;
if (EnableSuggestions && AutoCompleteModal.CheckEscape(Completer))
OnAutocompleteEscaped();
@ -230,23 +278,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 +311,24 @@ namespace UnityExplorer.UI.CSConsole
UpdateCaret(out bool caretMoved);
if (!settingCaretCoroutine && EnableSuggestions && AutoCompleteModal.CheckEscape(Completer))
if (!settingCaretCoroutine && EnableSuggestions)
{
OnAutocompleteEscaped();
return;
}
if (AutoCompleteModal.CheckEscape(Completer))
{
OnAutocompleteEscaped();
return;
}
if (!settingCaretCoroutine && EnableSuggestions && caretMoved)
{
AutoCompleteModal.Instance.ReleaseOwnership(Completer);
//Completer.CheckAutocompletes();
if (caretMoved)
AutoCompleteModal.Instance.ReleaseOwnership(Completer);
}
if (EnableCtrlRShortcut
&& (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
&& InputManager.GetKeyDown(KeyCode.R))
&& InputManager.GetKeyDown(KeyCode.R)
&& timeOfLastCtrlR.OccuredEarlierThanDefault())
{
timeOfLastCtrlR = Time.realtimeSinceStartup;
Evaluate(Panel.Input.Text);
}
}
@ -304,8 +363,8 @@ namespace UnityExplorer.UI.CSConsole
var charTop = charInfo.cursorPos.y;
var charBot = charTop - CSCONSOLE_LINEHEIGHT;
var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
var viewportMax = viewportMin - Panel.InputScroll.ViewportRect.rect.height;
var viewportMin = Input.Transform.rect.height - Input.Transform.anchoredPosition.y - (Input.Transform.rect.height * 0.5f);
var viewportMax = viewportMin - Panel.InputScroller.ViewportRect.rect.height;
float diff = 0f;
if (charTop > viewportMin)
@ -315,7 +374,7 @@ namespace UnityExplorer.UI.CSConsole
if (Math.Abs(diff) > 1)
{
var rect = Input.Rect;
var rect = Input.Transform;
rect.anchoredPosition = new Vector2(rect.anchoredPosition.x, rect.anchoredPosition.y - diff);
}
}
@ -325,20 +384,80 @@ namespace UnityExplorer.UI.CSConsole
{
settingCaretCoroutine = true;
Input.Component.readOnly = true;
RuntimeProvider.Instance.StartCoroutine(SetAutocompleteCaretCoro(caretPosition));
RuntimeHelper.StartCoroutine(SetCaretCoroutine(caretPosition));
}
private static IEnumerator SetAutocompleteCaretCoro(int caretPosition)
static void InitEventSystemPropertyHandlers()
{
try
{
foreach (var member in typeof(EventSystem).GetMembers(AccessTools.all))
{
if (member.Name == "m_CurrentSelected")
{
Type backingType;
if (member.MemberType == MemberTypes.Property)
backingType = (member as PropertyInfo).PropertyType;
else
backingType = (member as FieldInfo).FieldType;
usingEventSystemDictionaryMembers = ReflectionUtility.IsDictionary(backingType);
break;
}
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception checking EventSystem property backing type: {ex}");
}
}
static bool usingEventSystemDictionaryMembers;
static readonly AmbiguousMemberHandler<EventSystem, GameObject> m_CurrentSelected_Handler_Normal = new("m_CurrentSelected", "m_currentSelected");
static readonly AmbiguousMemberHandler<EventSystem, Dictionary<int, GameObject>> m_CurrentSelected_Handler_Dictionary = new("m_CurrentSelected", "m_currentSelected");
static readonly AmbiguousMemberHandler<EventSystem, bool> m_SelectionGuard_Handler_Normal = new("m_SelectionGuard", "m_selectionGuard");
static readonly AmbiguousMemberHandler<EventSystem, Dictionary<int, bool>> m_SelectionGuard_Handler_Dictionary = new("m_SelectionGuard", "m_selectionGuard");
static void SetCurrentSelectedGameObject(EventSystem instance, GameObject value)
{
instance.SetSelectedGameObject(value);
if (usingEventSystemDictionaryMembers)
m_CurrentSelected_Handler_Dictionary.GetValue(instance)[0] = value;
else
m_CurrentSelected_Handler_Normal.SetValue(instance, value);
}
static void SetSelectionGuard(EventSystem instance, bool value)
{
if (usingEventSystemDictionaryMembers)
m_SelectionGuard_Handler_Dictionary.GetValue(instance)[0] = value;
else
m_SelectionGuard_Handler_Normal.SetValue(instance, value);
}
private static IEnumerator SetCaretCoroutine(int caretPosition)
{
var color = Input.Component.selectionColor;
color.a = 0f;
Input.Component.selectionColor = color;
EventSystem.current.SetSelectedGameObject(null, null);
yield return null;
EventSystem.current.SetSelectedGameObject(Input.UIRoot, null);
try { SetCurrentSelectedGameObject(CursorUnlocker.CurrentEventSystem, null); }
catch (Exception ex) { ExplorerCore.Log($"Failed removing selected object: {ex}"); }
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
try { SetSelectionGuard(CursorUnlocker.CurrentEventSystem, false); }
catch (Exception ex) { ExplorerCore.Log($"Failed setting selection guard: {ex}"); }
try { SetCurrentSelectedGameObject(CursorUnlocker.CurrentEventSystem, Input.GameObject); }
catch (Exception ex) { ExplorerCore.Log($"Failed setting selected gameobject: {ex}"); }
yield return null; // ~~~~~~~ YIELD FRAME ~~~~~~~~~
Input.Component.Select();
yield return null;
Input.Component.caretPosition = caretPosition;
Input.Component.selectionFocusPosition = caretPosition;
@ -351,48 +470,91 @@ namespace UnityExplorer.UI.CSConsole
settingCaretCoroutine = false;
}
#region Lexer Highlighting
private static void HighlightVisibleInput()
/// <summary>
/// Returns true if caret is inside string or comment, false otherwise
/// </summary>
private static bool HighlightVisibleInput()
{
int startIdx = 0;
int endIdx = Input.Text.Length - 1;
int topLine = 0;
// Calculate visible text if necessary
if (Input.Rect.rect.height > Panel.InputScroll.ViewportRect.rect.height)
if (string.IsNullOrEmpty(Input.Text))
{
topLine = -1;
int bottomLine = -1;
// the top and bottom position of the viewport in relation to the text height
// they need the half-height adjustment to normalize against the 'line.topY' value.
var viewportMin = Input.Rect.rect.height - Input.Rect.anchoredPosition.y - (Input.Rect.rect.height * 0.5f);
var viewportMax = viewportMin - Panel.InputScroll.ViewportRect.rect.height;
for (int i = 0; i < Input.TextGenerator.lineCount; i++)
{
var line = Input.TextGenerator.lines[i];
// if not set the top line yet, and top of line is below the viewport top
if (topLine == -1 && line.topY <= viewportMin)
topLine = i;
// if bottom of line is below the viewport bottom
if ((line.topY - line.height) >= viewportMax)
bottomLine = i;
}
topLine = Math.Max(0, topLine - 1);
bottomLine = Math.Min(Input.TextGenerator.lineCount - 1, bottomLine + 1);
startIdx = Input.TextGenerator.lines[topLine].startCharIdx;
endIdx = (bottomLine >= Input.TextGenerator.lineCount - 1)
? Input.Text.Length - 1
: (Input.TextGenerator.lines[bottomLine + 1].startCharIdx - 1);
Panel.HighlightText.text = "";
Panel.LineNumberText.text = "1";
return false;
}
// Calculate the visible lines
int topLine = -1;
int bottomLine = -1;
// the top and bottom position of the viewport in relation to the text height
// they need the half-height adjustment to normalize against the 'line.topY' value.
var viewportMin = Input.Transform.rect.height - Input.Transform.anchoredPosition.y - (Input.Transform.rect.height * 0.5f);
var viewportMax = viewportMin - Panel.InputScroller.ViewportRect.rect.height;
for (int i = 0; i < Input.TextGenerator.lineCount; i++)
{
var line = Input.TextGenerator.lines[i];
// if not set the top line yet, and top of line is below the viewport top
if (topLine == -1 && line.topY <= viewportMin)
topLine = i;
// if bottom of line is below the viewport bottom
if ((line.topY - line.height) >= viewportMax)
bottomLine = i;
}
topLine = Math.Max(0, topLine - 1);
bottomLine = Math.Min(Input.TextGenerator.lineCount - 1, bottomLine + 1);
int startIdx = Input.TextGenerator.lines[topLine].startCharIdx;
int endIdx = (bottomLine >= Input.TextGenerator.lineCount - 1)
? Input.Text.Length - 1
: (Input.TextGenerator.lines[bottomLine + 1].startCharIdx - 1);
// Highlight the visible text with the LexerBuilder
Panel.HighlightText.text = Lexer.BuildHighlightedString(Input.Text, startIdx, endIdx, topLine);
Panel.HighlightText.text = Lexer.BuildHighlightedString(Input.Text, startIdx, endIdx, topLine, LastCaretPosition, out bool ret);
// Set the line numbers
// determine true starting line number (not the same as the cached TextGenerator line numbers)
int realStartLine = 0;
for (int i = 0; i < startIdx; i++)
{
if (LexerBuilder.IsNewLine(Input.Text[i]))
realStartLine++;
}
realStartLine++;
char lastPrev = '\n';
var sb = new StringBuilder();
// append leading new lines for spacing (no point rendering line numbers we cant see)
for (int i = 0; i < topLine; i++)
sb.Append('\n');
// append the displayed line numbers
for (int i = topLine; i <= bottomLine; i++)
{
if (i > 0)
lastPrev = Input.Text[Input.TextGenerator.lines[i].startCharIdx - 1];
// previous line ended with a newline character, this is an actual new line.
if (LexerBuilder.IsNewLine(lastPrev))
{
sb.Append(realStartLine.ToString());
realStartLine++;
}
sb.Append('\n');
}
Panel.LineNumberText.text = sb.ToString();
return ret;
}
#endregion
@ -494,7 +656,7 @@ If the game was built with Unity's stubbed netstandard 2.0 runtime, you can fix
}
}
private static readonly Dictionary<string, string> helpDict = new Dictionary<string, string>();
private static readonly Dictionary<string, string> helpDict = new();
public static void SetupHelpInteraction()
{
@ -526,7 +688,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.
@ -547,15 +711,18 @@ var x = 5;
++x;
/* The following helpers are available in REPL mode:
* CurrentTarget; - System.Object, the target of the active Inspector tab
* AllTargets; - System.Object[], the targets of all Inspector tabs
* Log(obj); - prints a message to the console log
* Inspect(obj); - inspect the object with the Inspector
* Inspect(someType); - inspect a Type with static reflection
* Start(enumerator); - Coroutine, starts the IEnumerator as a Coroutine, and returns the Coroutine.
* Stop(coroutine); - stop the Coroutine ONLY if it was started with Start(ienumerator).
* Copy(obj); - copies the object to the UnityExplorer Clipboard
* Paste(); - System.Object, the contents of the Clipboard.
* GetUsing(); - prints the current using directives to the console log
* GetVars(); - prints the names and values of the REPL variables you have defined
* GetClasses(); - prints the names and members of the classes you have defined
* Log(obj); - prints a message to the console log
* CurrentTarget; - System.Object, the target of the active Inspector tab
* AllTargets; - System.Object[], the targets of all Inspector tabs
* Inspect(obj); - inspect the object with the Inspector
* Inspect(someType); - inspect a Type with static reflection
* Start(enumerator); - starts the IEnumerator as a Coroutine
* help; - the default REPL help command, contains additional helpers.
*/";
@ -563,7 +730,7 @@ var x = 5;
// You can soft-overwrite a class by compiling it again with the same name. The old class will still technically exist in memory.
// Compiled classes can be accessed from both inside and outside this console.
// Note: in IL2CPP, injecting these classes with ClassInjector may crash the game!
// Note: in IL2CPP, you must declare a Namespace to inject these classes with ClassInjector or it will crash the game.
public class HelloWorld
{

View File

@ -5,14 +5,18 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.CSConsole.Lexers;
using UnityExplorer.CSConsole.Lexers;
using UniverseLib;
using UniverseLib.Utility;
namespace UnityExplorer.UI.CSConsole
namespace UnityExplorer.CSConsole
{
public struct MatchInfo
{
public int startIndex;
public int endIndex;
public bool isStringOrComment;
public bool matchToEndOfLine;
public string htmlColorTag;
}
@ -82,8 +86,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 +111,27 @@ namespace UnityExplorer.UI.CSConsole
// append the highlighted match
sb.Append(match.htmlColorTag);
for (int i = match.startIndex; i <= match.endIndex && i <= currentEndIdx; i++)
sb.Append(input[i]);
sb.Append(SignatureHighlighter.CLOSE_COLOR);
// update the last unhighlighted start index
lastUnhighlighted = match.endIndex + 1;
int matchEndIdx = match.endIndex;
if (match.matchToEndOfLine)
{
while (input.Length - 1 >= matchEndIdx)
{
matchEndIdx++;
if (IsNewLine(input[matchEndIdx]))
break;
}
}
// check caretIdx to determine inStringOrComment state
if (caretIdx >= match.startIndex && (caretIdx <= (matchEndIdx+1) || (caretIdx >= input.Length && matchEndIdx >= input.Length - 1)))
caretInStringOrComment = match.isStringOrComment;
}
// Append trailing unhighlighted input
@ -150,6 +169,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
{
@ -44,7 +44,7 @@ namespace UnityExplorer.UI.CSConsole.Lexers
while (!lexer.EndOfInput && !(lexer.Current == '/' && lexer.Previous == '*'));
return true;
}
}
}
return false;

View File

@ -2,7 +2,7 @@
using System.Text;
using UnityEngine;
namespace UnityExplorer.UI.CSConsole.Lexers
namespace UnityExplorer.CSConsole.Lexers
{
public class KeywordLexer : Lexer
{
@ -12,12 +12,12 @@ namespace UnityExplorer.UI.CSConsole.Lexers
public static readonly HashSet<string> keywords = new HashSet<string>
{
// reserved keywords
"abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue",
"abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue",
"decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally",
"fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock",
"long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public",
"readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch",
"this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void",
"fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock",
"long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public",
"readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch",
"this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void",
"volatile", "while",
// contextual keywords
"add", "and", "alias", "ascending", "async", "await", "by", "descending", "dynamic", "equals", "from", "get",
@ -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,10 @@
using System.Collections.Generic;
using UnityEngine;
using System.Linq;
using UnityEngine;
using UniverseLib;
using UniverseLib.Utility;
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
{
@ -16,7 +16,7 @@ namespace UnityExplorer.UI.CSConsole
"mscorlib", "System.Core", "System", "System.Xml"
};
internal static TextWriter _textWriter;
internal TextWriter _textWriter;
internal static StreamReportPrinter _reportPrinter;
public ScriptEvaluator(TextWriter tw) : base(BuildContext(tw))
@ -51,8 +51,13 @@ namespace UnityExplorer.UI.CSConsole
ReferenceAssembly(asm);
}
private static CompilerContext context;
private static CompilerContext BuildContext(TextWriter tw)
{
if (context != null)
return context;
_reportPrinter = new StreamReportPrinter(tw);
var settings = new CompilerSettings
@ -65,7 +70,7 @@ namespace UnityExplorer.UI.CSConsole
EnhancedWarnings = false
};
return new CompilerContext(settings, _reportPrinter);
return context = new CompilerContext(settings, _reportPrinter);
}
private static void ImportAppdomainAssemblies(Action<Assembly> import)

View File

@ -1,50 +1,48 @@
using System;
using HarmonyLib;
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.Runtime;
using UnityExplorer.UI.Panels;
using UniverseLib;
/*
Welcome to the UnityExplorer C# Console!
Use the Help dropdown to see detailed examples of how to use this console.
To see your output, use the Log panel or a Console Log window.
*/
namespace UnityExplorer.UI.CSConsole
namespace UnityExplorer.CSConsole
{
public class ScriptInteraction : InteractiveBase
{
public static object CurrentTarget
=> InspectorManager.ActiveInspector?.Target;
public static object[] AllTargets
=> InspectorManager.Inspectors.Select(it => it.Target).ToArray();
public static void Log(object message)
{
ExplorerCore.Log(message);
}
public static object CurrentTarget => InspectorManager.ActiveInspector?.Target;
public static object[] AllTargets => InspectorManager.Inspectors.Select(it => it.Target).ToArray();
=> ExplorerCore.Log(message);
public static void Inspect(object obj)
{
InspectorManager.Inspect(obj);
}
=> InspectorManager.Inspect(obj);
public static void Inspect(Type type)
{
InspectorManager.Inspect(type);
}
=> InspectorManager.Inspect(type);
public static void Start(IEnumerator ienumerator)
{
RuntimeProvider.Instance.StartCoroutine(ienumerator);
}
public static Coroutine Start(IEnumerator ienumerator)
=> RuntimeHelper.StartCoroutine(ienumerator);
public static void Stop(Coroutine coro)
=> RuntimeHelper.StopCoroutine(coro);
public static void Copy(object obj)
=> ClipboardPanel.Copy(obj);
public static object Paste()
=> ClipboardPanel.Current;
public static void GetUsing()
{
Log(Evaluator.GetUsing());
}
=> Log(Evaluator.GetUsing());
public static void GetVars()
{
@ -57,8 +55,8 @@ namespace UnityExplorer.UI.CSConsole
public static void GetClasses()
{
if (ReflectionUtility.GetFieldInfo(typeof(Evaluator), "source_file")
.GetValue(Evaluator) is CompilationSourceFile sourceFile
if (AccessTools.Field(typeof(Evaluator), "source_file")
.GetValue(Evaluator) is CompilationSourceFile sourceFile
&& sourceFile.Containers.Any())
{
var sb = new StringBuilder();
@ -76,4 +74,4 @@ namespace UnityExplorer.UI.CSConsole
}
}
}
}

View File

@ -2,21 +2,21 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityExplorer.Core.Config;
using UnityExplorer.UI.CacheObject.Views;
using UnityExplorer.Config;
using UnityExplorer.CacheObject.Views;
namespace UnityExplorer.UI.CacheObject
namespace UnityExplorer.CacheObject
{
public class CacheConfigEntry : CacheObjectBase
{
public CacheConfigEntry(IConfigElement configElement)
{
this.RefConfigElement = configElement;
this.FallbackType = configElement.ElementType;
this.NameLabelText = $"<color=cyan>{configElement.Name}</color>" +
$"\r\n<color=grey><i>{configElement.Description}</i></color>";
this.FallbackType = configElement.ElementType;
this.NameLabelTextRaw = string.Empty;
configElement.OnValueChangedNotify += UpdateValueFromSource;
}
@ -44,6 +44,6 @@ namespace UnityExplorer.UI.CacheObject
RefConfigElement.BoxedValue = value;
}
protected override bool SetCellEvaluateState(CacheObjectCell cell) => false;
protected override bool TryAutoEvaluateIfUnitialized(CacheObjectCell cell) => true;
}
}

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,12 @@
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;
using UniverseLib;
using UniverseLib.Utility;
namespace UnityExplorer.UI.CacheObject
namespace UnityExplorer.CacheObject
{
public class CacheKeyValuePair : CacheObjectBase
{
@ -62,6 +63,7 @@ namespace UnityExplorer.UI.CacheObject
var kvpCell = cell as CacheKeyValuePairCell;
kvpCell.NameLabel.text = $"{DictIndex}:";
kvpCell.HiddenNameLabel.Text = "";
kvpCell.Image.color = DictIndex % 2 == 0 ? CacheListEntryCell.EvenColor : CacheListEntryCell.OddColor;
if (KeyInputWanted)
@ -91,10 +93,6 @@ namespace UnityExplorer.UI.CacheObject
}
protected override bool SetCellEvaluateState(CacheObjectCell cell)
{
// not needed
return false;
}
protected override bool TryAutoEvaluateIfUnitialized(CacheObjectCell cell) => true;
}
}

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
{
@ -13,7 +13,7 @@ namespace UnityExplorer.UI.CacheObject
public override bool ShouldAutoEvaluate => true;
public override bool HasArguments => false;
public override bool CanWrite => Owner.CanWrite;
public override bool CanWrite => Owner?.CanWrite ?? false;
public void SetListOwner(InteractiveList list, int listIndex)
{
@ -28,6 +28,7 @@ namespace UnityExplorer.UI.CacheObject
var listCell = cell as CacheListEntryCell;
listCell.NameLabel.text = $"{ListIndex}:";
listCell.HiddenNameLabel.Text = "";
listCell.Image.color = ListIndex % 2 == 0 ? CacheListEntryCell.EvenColor : CacheListEntryCell.OddColor;
}
@ -36,11 +37,6 @@ namespace UnityExplorer.UI.CacheObject
(Owner as InteractiveList).TrySetValueToIndex(value, this.ListIndex);
}
protected override bool SetCellEvaluateState(CacheObjectCell cell)
{
// not needed
return false;
}
protected override bool TryAutoEvaluateIfUnitialized(CacheObjectCell cell) => true;
}
}

View File

@ -4,19 +4,21 @@ using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityExplorer.Core.Runtime;
using UnityExplorer.UI.CacheObject.Views;
using UnityExplorer.UI.Inspectors;
using UnityExplorer.UI.ObjectPool;
using UnityExplorer.UI.Utility;
using UnityExplorer.Runtime;
using UnityExplorer.CacheObject.Views;
using UnityExplorer.Inspectors;
using UniverseLib.UI.Models;
using UnityExplorer.UI;
using UniverseLib;
using UniverseLib.UI;
using UnityExplorer.UI.Widgets;
using UniverseLib.Utility;
using UniverseLib.UI.ObjectPool;
namespace UnityExplorer.UI.CacheObject
namespace UnityExplorer.CacheObject
{
public abstract class CacheMember : CacheObjectBase
{
//public ReflectionInspector ParentInspector { get; internal set; }
//public bool AutoUpdateWanted { get; internal set; }
public abstract Type DeclaringType { get; }
public string NameForFiltering { get; protected set; }
public object DeclaringInstance => IsStatic ? null : (m_declaringInstance ?? (m_declaringInstance = Owner.Target.TryCast(DeclaringType)));
@ -28,12 +30,15 @@ namespace UnityExplorer.UI.CacheObject
public Type[] GenericArguments { get; protected set; } = ArgumentUtility.EmptyTypes;
public EvaluateWidget Evaluator { get; protected set; }
public bool Evaluating => Evaluator != null && Evaluator.UIRoot.activeSelf;
public virtual void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member)
{
this.Owner = inspector;
this.NameLabelText = SignatureHighlighter.Parse(member.DeclaringType, false, member);
this.NameLabelText = this is CacheMethod
? SignatureHighlighter.HighlightMethod(member as MethodInfo)
: SignatureHighlighter.Parse(member.DeclaringType, false, member);
this.NameForFiltering = $"{member.DeclaringType.Name}.{member.Name}";
this.NameLabelTextRaw = NameForFiltering;
}
public override void ReleasePooledObjects()
@ -60,6 +65,17 @@ namespace UnityExplorer.UI.CacheObject
protected abstract void TrySetValue(object value);
/// <summary>
/// Evaluate is called when first shown (if ShouldAutoEvaluate), or else when Evaluate button is clicked, or auto-updated.
/// </summary>
public void Evaluate()
{
SetValueFromSource(TryEvaluate());
}
/// <summary>
/// Called when user presses the Evaluate button.
/// </summary>
public void EvaluateAndSetCell()
{
Evaluate();
@ -67,40 +83,27 @@ namespace UnityExplorer.UI.CacheObject
SetDataToCell(CellView);
}
/// <summary>
/// Evaluate when first shown (if ShouldAutoEvaluate), or else when Evaluate button is clicked, or auto-updated.
/// </summary>
public void Evaluate()
{
SetValueFromSource(TryEvaluate());
}
public override void TrySetUserValue(object value)
{
TrySetValue(value);
Evaluate();
}
protected override void SetValueState(CacheObjectCell cell, ValueStateArgs args)
{
base.SetValueState(cell, args);
//var memCell = cell as CacheMemberCell;
//memCell.UpdateToggle.gameObject.SetActive(ShouldAutoEvaluate);
}
private static readonly Color evalEnabledColor = new Color(0.15f, 0.25f, 0.15f);
private static readonly Color evalDisabledColor = new Color(0.15f, 0.15f, 0.15f);
protected override bool SetCellEvaluateState(CacheObjectCell objectcell)
protected override bool TryAutoEvaluateIfUnitialized(CacheObjectCell objectcell)
{
var cell = objectcell as CacheMemberCell;
cell.EvaluateHolder.SetActive(!ShouldAutoEvaluate);
if (!ShouldAutoEvaluate)
{
//cell.UpdateToggle.gameObject.SetActive(false);
cell.EvaluateButton.Component.gameObject.SetActive(true);
if (HasArguments)
{
@ -110,36 +113,30 @@ namespace UnityExplorer.UI.CacheObject
{
cell.EvaluateButton.ButtonText.text = "Hide";
Evaluator.UIRoot.transform.SetParent(cell.EvaluateHolder.transform, false);
RuntimeProvider.Instance.SetColorBlock(cell.EvaluateButton.Component, evalEnabledColor, evalEnabledColor * 1.3f);
RuntimeHelper.SetColorBlock(cell.EvaluateButton.Component, evalEnabledColor, evalEnabledColor * 1.3f);
}
}
else
cell.EvaluateButton.ButtonText.text = "Evaluate";
if (!Evaluating)
RuntimeProvider.Instance.SetColorBlock(cell.EvaluateButton.Component, evalDisabledColor, evalDisabledColor * 1.3f);
RuntimeHelper.SetColorBlock(cell.EvaluateButton.Component, evalDisabledColor, evalDisabledColor * 1.3f);
}
//else
//{
// cell.UpdateToggle.gameObject.SetActive(true);
// cell.UpdateToggle.isOn = AutoUpdateWanted;
//}
if (State == ValueState.NotEvaluated && !ShouldAutoEvaluate)
{
SetValueState(cell, ValueStateArgs.Default);
cell.RefreshSubcontentButton();
return true;
return false;
}
if (State == ValueState.NotEvaluated)
Evaluate();
return false;
return true;
}
public void OnEvaluateClicked()
{
if (!HasArguments)
@ -153,7 +150,7 @@ namespace UnityExplorer.UI.CacheObject
this.Evaluator = Pool<EvaluateWidget>.Borrow();
Evaluator.OnBorrowedFromPool(this);
Evaluator.UIRoot.transform.SetParent((CellView as CacheMemberCell).EvaluateHolder.transform, false);
SetCellEvaluateState(CellView);
TryAutoEvaluateIfUnitialized(CellView);
}
else
{
@ -162,7 +159,7 @@ namespace UnityExplorer.UI.CacheObject
else
Evaluator.UIRoot.SetActive(true);
SetCellEvaluateState(CellView);
TryAutoEvaluateIfUnitialized(CellView);
}
}
}
@ -170,22 +167,22 @@ namespace UnityExplorer.UI.CacheObject
#region Cache Member Util
public static bool CanParseArgs(ParameterInfo[] parameters)
{
foreach (var param in parameters)
{
var pType = param.ParameterType;
if (pType.IsByRef && pType.HasElementType)
pType = pType.GetElementType();
if (pType != null && ParseUtility.CanParse(pType))
continue;
else
return false;
}
return true;
}
//public static bool CanParseArgs(ParameterInfo[] parameters)
//{
// foreach (var param in parameters)
// {
// var pType = param.ParameterType;
//
// if (pType.IsByRef && pType.HasElementType)
// pType = pType.GetElementType();
//
// if (pType != null && ParseUtility.CanParse(pType))
// continue;
// else
// return false;
// }
// return true;
//}
public static List<CacheMember> GetCacheMembers(object inspectorTarget, Type _type, ReflectionInspector _inspector)
{
@ -235,17 +232,17 @@ namespace UnityExplorer.UI.CacheObject
return sorted;
}
private static void TryCacheMember(MemberInfo member, List<CacheMember> list, HashSet<string> cachedSigs,
private static void TryCacheMember(MemberInfo member, List<CacheMember> list, HashSet<string> cachedSigs,
Type declaringType, ReflectionInspector _inspector, bool ignorePropertyMethodInfos = true)
{
try
{
if (ReflectionUtility.IsBlacklisted(member))
if (UERuntimeHelper.IsBlacklisted(member))
return;
var sig = GetSig(member);
//ExplorerCore.Log($"Trying to cache member {sig}...");
//ExplorerCore.Log($"Trying to cache member {sig}... ({member.MemberType})");
CacheMember cached;
Type returnType;
@ -254,15 +251,15 @@ namespace UnityExplorer.UI.CacheObject
case MemberTypes.Method:
{
var mi = member as MethodInfo;
if (ignorePropertyMethodInfos
if (ignorePropertyMethodInfos
&& (mi.Name.StartsWith("get_") || mi.Name.StartsWith("set_")))
return;
var args = mi.GetParameters();
if (!CanParseArgs(args))
return;
//var args = mi.GetParameters();
//if (!CanParseArgs(args))
// return;
sig += GetArgumentString(args);
sig += GetArgumentString(mi.GetParameters());
if (cachedSigs.Contains(sig))
return;
@ -275,9 +272,9 @@ namespace UnityExplorer.UI.CacheObject
{
var pi = member as PropertyInfo;
var args = pi.GetIndexParameters();
if (!CanParseArgs(args))
return;
//var args = pi.GetIndexParameters();
//if (!CanParseArgs(args))
// return;
if (!pi.CanRead && pi.CanWrite)
{
@ -288,7 +285,7 @@ namespace UnityExplorer.UI.CacheObject
return;
}
sig += GetArgumentString(args);
sig += GetArgumentString(pi.GetIndexParameters());
if (cachedSigs.Contains(sig))
return;
@ -310,7 +307,6 @@ namespace UnityExplorer.UI.CacheObject
cachedSigs.Add(sig);
//cached.Initialize(_inspector, declaringType, member, returnType);
cached.SetFallbackType(returnType);
cached.SetInspectorOwner(_inspector, member);
@ -323,7 +319,7 @@ namespace UnityExplorer.UI.CacheObject
}
}
internal static string GetSig(MemberInfo member)
internal static string GetSig(MemberInfo member)
=> $"{member.DeclaringType.Name}.{member.Name}";
internal static string GetArgumentString(ParameterInfo[] args)

View File

@ -3,9 +3,11 @@ using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityExplorer.UI.Inspectors;
using UnityExplorer.Inspectors;
using UniverseLib;
using UniverseLib.Utility;
namespace UnityExplorer.UI.CacheObject
namespace UnityExplorer.CacheObject
{
public class CacheMethod : CacheMember
{
@ -30,15 +32,14 @@ namespace UnityExplorer.UI.CacheObject
try
{
var methodInfo = MethodInfo;
if (methodInfo.IsGenericMethod)
methodInfo = MethodInfo.MakeGenericMethod(Evaluator.TryParseGenericArguments());
if (Arguments.Length > 0)
return methodInfo.Invoke(DeclaringInstance, Evaluator.TryParseArguments());
var ret = methodInfo.Invoke(DeclaringInstance, ArgumentUtility.EmptyArgs);
object ret;
if (HasArguments)
ret = methodInfo.Invoke(DeclaringInstance, Evaluator.TryParseArguments());
else
ret = methodInfo.Invoke(DeclaringInstance, ArgumentUtility.EmptyArgs);
HadException = false;
LastException = null;
return ret;

View File

@ -6,13 +6,17 @@ using System.Reflection;
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.Runtime;
using UnityExplorer.CacheObject.IValues;
using UnityExplorer.CacheObject.Views;
using UniverseLib.UI.Models;
using UnityExplorer.UI;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.Utility;
using UniverseLib.UI.ObjectPool;
namespace UnityExplorer.UI.CacheObject
namespace UnityExplorer.CacheObject
{
public enum ValueState
{
@ -47,6 +51,7 @@ namespace UnityExplorer.UI.CacheObject
public bool SubContentShowWanted { get; private set; }
public string NameLabelText { get; protected set; }
public string NameLabelTextRaw { get; protected set; }
public string ValueLabelText { get; protected set; }
public abstract bool ShouldAutoEvaluate { get; }
@ -181,8 +186,8 @@ namespace UnityExplorer.UI.CacheObject
return ValueState.Enum;
else if (type == typeof(Color) || type == typeof(Color32))
return ValueState.Color;
else if (InteractiveValueStruct.SupportsType(type))
return ValueState.ValueStruct;
else if (InteractiveValueStruct.SupportsType(type))
return ValueState.ValueStruct;
else if (ReflectionUtility.IsDictionary(type))
return ValueState.Dictionary;
else if (!typeof(Transform).IsAssignableFrom(type) && ReflectionUtility.IsEnumerable(type))
@ -198,7 +203,7 @@ namespace UnityExplorer.UI.CacheObject
switch (State)
{
case ValueState.NotEvaluated:
return $"<i>{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})</i>";
return $"<i>{NOT_YET_EVAL} ({SignatureHighlighter.Parse(FallbackType, true)})</i>";
case ValueState.Exception:
return $"<i><color=red>{LastException.ReflectionExToString()}</color></i>";
@ -217,12 +222,9 @@ namespace UnityExplorer.UI.CacheObject
// string wants it trimmed to max 200 chars
case ValueState.String:
if (!LastValueWasNull)
{
string s = Value as string;
return $"\"{ToStringUtility.PruneString(s, 200, 5)}\"";
}
return $"\"{ToStringUtility.PruneString(Value as string, 200, 5)}\"";
break;
// try to prefix the count of the collection for lists and dicts
case ValueState.Collection:
if (!LastValueWasNull)
@ -254,24 +256,38 @@ namespace UnityExplorer.UI.CacheObject
// Setting cell state from our model
/// <summary>Return true if SetCell should abort, false if it should continue.</summary>
protected abstract bool SetCellEvaluateState(CacheObjectCell cell);
/// <summary>Return false if SetCell should abort, true if it should continue.</summary>
protected abstract bool TryAutoEvaluateIfUnitialized(CacheObjectCell cell);
public virtual void SetDataToCell(CacheObjectCell cell)
{
cell.NameLabel.text = NameLabelText;
if (cell.HiddenNameLabel != null)
cell.HiddenNameLabel.Text = NameLabelTextRaw ?? string.Empty;
cell.ValueLabel.gameObject.SetActive(true);
cell.SubContentHolder.gameObject.SetActive(SubContentShowWanted);
if (IValue != null)
{
{
IValue.UIRoot.transform.SetParent(cell.SubContentHolder.transform, false);
IValue.SetLayout();
}
if (SetCellEvaluateState(cell))
bool evaluated = TryAutoEvaluateIfUnitialized(cell);
if (cell.CopyButton != null)
{
bool hasEvaluated = State != ValueState.NotEvaluated && State != ValueState.Exception;
cell.CopyButton.Component.gameObject.SetActive(hasEvaluated);
cell.PasteButton.Component.gameObject.SetActive(hasEvaluated && this.CanWrite);
}
if (!evaluated)
return;
// The following only executes if the object has evaluated.
// For members and properties with args, they will return by default now.
switch (State)
{
case ValueState.Exception:
@ -439,7 +455,7 @@ namespace UnityExplorer.UI.CacheObject
{
inactiveIValueHolder = new GameObject("Temp_IValue_Holder");
GameObject.DontDestroyOnLoad(inactiveIValueHolder);
inactiveIValueHolder.transform.parent = UIManager.PoolHolder.transform;
inactiveIValueHolder.transform.parent = UniversalUI.PoolHolder.transform;
inactiveIValueHolder.SetActive(false);
}
return inactiveIValueHolder;

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,13 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.CacheObject;
using UnityExplorer.CacheObject;
using UnityExplorer.UI;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib;
namespace UnityExplorer.UI.IValues
namespace UnityExplorer.CacheObject.IValues
{
public class InteractiveColor : InteractiveValue
{
@ -14,11 +18,11 @@ namespace UnityExplorer.UI.IValues
public Color EditedColor;
private Image m_colorImage;
private readonly InputFieldRef[] m_inputs = new InputFieldRef[4];
private readonly Slider[] m_sliders = new Slider[4];
private Image colorImage;
private readonly InputFieldRef[] inputs = new InputFieldRef[4];
private readonly Slider[] sliders = new Slider[4];
private ButtonRef m_applyButton;
private ButtonRef applyButton;
private static readonly string[] fieldNames = new[] { "R", "G", "B", "A" };
@ -26,11 +30,11 @@ namespace UnityExplorer.UI.IValues
{
base.OnBorrowed(owner);
m_applyButton.Component.gameObject.SetActive(owner.CanWrite);
applyButton.Component.gameObject.SetActive(owner.CanWrite);
foreach (var slider in m_sliders)
foreach (var slider in sliders)
slider.interactable = owner.CanWrite;
foreach (var input in m_inputs)
foreach (var input in inputs)
input.Component.readOnly = !owner.CanWrite;
}
@ -46,27 +50,27 @@ namespace UnityExplorer.UI.IValues
{
IsValueColor32 = true;
EditedColor = c32;
m_inputs[0].Text = c32.r.ToString();
m_inputs[1].Text = c32.g.ToString();
m_inputs[2].Text = c32.b.ToString();
m_inputs[3].Text = c32.a.ToString();
foreach (var slider in m_sliders)
inputs[0].Text = c32.r.ToString();
inputs[1].Text = c32.g.ToString();
inputs[2].Text = c32.b.ToString();
inputs[3].Text = c32.a.ToString();
foreach (var slider in sliders)
slider.maxValue = 255;
}
else
{
IsValueColor32 = false;
EditedColor = (Color)value;
m_inputs[0].Text = EditedColor.r.ToString();
m_inputs[1].Text = EditedColor.g.ToString();
m_inputs[2].Text = EditedColor.b.ToString();
m_inputs[3].Text = EditedColor.a.ToString();
foreach (var slider in m_sliders)
inputs[0].Text = EditedColor.r.ToString();
inputs[1].Text = EditedColor.g.ToString();
inputs[2].Text = EditedColor.b.ToString();
inputs[3].Text = EditedColor.a.ToString();
foreach (var slider in sliders)
slider.maxValue = 1;
}
if (m_colorImage)
m_colorImage.color = EditedColor;
if (colorImage)
colorImage.color = EditedColor;
}
// setting value to owner
@ -89,8 +93,8 @@ namespace UnityExplorer.UI.IValues
case 3: EditedColor.a = val; break;
}
if (m_colorImage)
m_colorImage.color = EditedColor;
if (colorImage)
colorImage.color = EditedColor;
}
private void OnInputChanged(string val, int fieldIndex)
@ -101,13 +105,13 @@ namespace UnityExplorer.UI.IValues
if (IsValueColor32)
{
byte value = byte.Parse(val);
m_sliders[fieldIndex].value = value;
sliders[fieldIndex].value = value;
f = (float)((decimal)value / 255);
}
else
{
f = float.Parse(val);
m_sliders[fieldIndex].value = f;
sliders[fieldIndex].value = f;
}
SetColorField(f, fieldIndex);
@ -127,12 +131,12 @@ namespace UnityExplorer.UI.IValues
{
if (IsValueColor32)
{
m_inputs[fieldIndex].Text = ((byte)val).ToString();
inputs[fieldIndex].Text = ((byte)val).ToString();
val /= 255f;
}
else
{
m_inputs[fieldIndex].Text = val.ToString();
inputs[fieldIndex].Text = val.ToString();
}
SetColorField(val, fieldIndex);
@ -165,15 +169,15 @@ namespace UnityExplorer.UI.IValues
// apply button
m_applyButton = UIFactory.CreateButton(horiGroup, "ApplyButton", "Apply", new Color(0.2f, 0.26f, 0.2f));
UIFactory.SetLayoutElement(m_applyButton.Component.gameObject, minHeight: 25, minWidth: 90);
m_applyButton.OnClick += SetValueToOwner;
applyButton = UIFactory.CreateButton(horiGroup, "ApplyButton", "Apply", new Color(0.2f, 0.26f, 0.2f));
UIFactory.SetLayoutElement(applyButton.Component.gameObject, minHeight: 25, minWidth: 90);
applyButton.OnClick += SetValueToOwner;
// image of color
var imgObj = UIFactory.CreateUIObject("ColorImageHelper", horiGroup);
UIFactory.SetLayoutElement(imgObj, minHeight: 25, minWidth: 50, flexibleWidth: 50);
m_colorImage = imgObj.AddComponent<Image>();
colorImage = imgObj.AddComponent<Image>();
return UIRoot;
}
@ -188,11 +192,11 @@ namespace UnityExplorer.UI.IValues
var input = UIFactory.CreateInputField(row, "Input", "...");
UIFactory.SetLayoutElement(input.UIRoot, minWidth: 40, minHeight: 25, flexibleHeight: 0);
m_inputs[index] = input;
inputs[index] = input;
input.OnValueChanged += (string val) => { OnInputChanged(val, index); };
var sliderObj = UIFactory.CreateSlider(row, "Slider", out Slider slider);
m_sliders[index] = slider;
sliders[index] = slider;
UIFactory.SetLayoutElement(sliderObj, minHeight: 25, minWidth: 70, flexibleWidth: 999, flexibleHeight: 0);
slider.minValue = 0;
slider.maxValue = 1;

View File

@ -5,14 +5,19 @@ using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.CacheObject;
using UnityExplorer.UI.CacheObject.Views;
using UnityExplorer.UI.Inspectors;
using UnityExplorer.CacheObject;
using UnityExplorer.CacheObject.Views;
using UnityExplorer.Inspectors;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Utility;
using UnityExplorer.UI.Widgets;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Widgets;
using UniverseLib.UI.Widgets.ScrollView;
using UniverseLib.Utility;
namespace UnityExplorer.UI.IValues
namespace UnityExplorer.CacheObject.IValues
{
public class InteractiveDictionary : InteractiveValue, ICellPoolDataSource<CacheKeyValuePairCell>, ICacheObjectController
{
@ -169,7 +174,6 @@ namespace UnityExplorer.UI.IValues
}
public int AdjustedWidth => (int)UIRect.rect.width - 80;
//public int AdjustedKeyWidth => HalfWidth - 50;
public override void SetLayout()
{

View File

@ -5,9 +5,15 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.CacheObject;
using UnityExplorer.CacheObject;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.Utility;
namespace UnityExplorer.UI.IValues
namespace UnityExplorer.CacheObject.IValues
{
public class InteractiveEnum : InteractiveValue
{
@ -18,10 +24,10 @@ namespace UnityExplorer.UI.IValues
public OrderedDictionary CurrentValues;
public CachedEnumValue ValueAtIdx(int idx) => (CachedEnumValue)CurrentValues[idx];
public CachedEnumValue ValueAtKey(object key) => (CachedEnumValue)CurrentValues[key];
private Dropdown enumDropdown;
private InputFieldRef inputField;
private ButtonRef enumHelperButton;
private EnumCompleter enumCompleter;
private GameObject toggleHolder;
private readonly List<Toggle> flagToggles = new List<Toggle>();
private readonly List<Text> flagTexts = new List<Text>();
@ -33,38 +39,68 @@ namespace UnityExplorer.UI.IValues
if (lastType != EnumType)
{
CurrentValues = GetEnumValues(EnumType, out IsFlags);
CurrentValues = GetEnumValues(EnumType);
IsFlags = EnumType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] fa && fa.Any();
if (IsFlags)
SetupTogglesForEnumType();
else
SetupDropdownForEnumType();
{
inputField.Component.gameObject.SetActive(true);
enumHelperButton.Component.gameObject.SetActive(true);
toggleHolder.SetActive(false);
}
enumCompleter.EnumType = EnumType;
enumCompleter.CacheEnumValues();
lastType = EnumType;
}
// setup ui for changes
if (IsFlags)
SetTogglesForValue(value);
if (!IsFlags)
inputField.Text = value.ToString();
else
SetDropdownForValue(value);
SetTogglesForValue(value);
this.enumCompleter.chosenSuggestion = value.ToString();
AutoCompleteModal.Instance.ReleaseOwnership(this.enumCompleter);
}
private void SetTogglesForValue(object value)
{
try
{
var split = value.ToString().Split(',');
var set = new HashSet<string>();
foreach (var s in split)
set.Add(s.Trim());
for (int i = 0; i < CurrentValues.Count; i++)
flagToggles[i].isOn = set.Contains(ValueAtIdx(i).Name);
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception setting flag toggles: " + ex);
}
}
// Setting value to owner
private void OnApplyClicked()
{
if (IsFlags)
SetValueFromFlags();
else
SetValueFromDropdown();
}
private void SetValueFromDropdown()
{
try
{
CurrentOwner.SetUserValue(ValueAtIdx(enumDropdown.value).ActualValue);
if (!IsFlags)
{
if (ParseUtility.TryParse(this.inputField.Text, EnumType, out object value, out Exception ex))
CurrentOwner.SetUserValue(value);
else
throw ex;
}
else
{
SetValueFromFlags();
}
}
catch (Exception ex)
{
@ -91,59 +127,53 @@ namespace UnityExplorer.UI.IValues
}
}
// setting UI state for value
// UI Construction
private void SetDropdownForValue(object value)
private void EnumHelper_OnClick()
{
if (CurrentValues.Contains(value))
{
var cached = ValueAtKey(value);
enumDropdown.value = cached.EnumIndex;
enumDropdown.RefreshShownValue();
}
else
ExplorerCore.LogWarning("CurrentValues does not contain key '" + value?.ToString() ?? "<null>" + "'");
enumCompleter.HelperButtonClicked();
}
private void SetTogglesForValue(object value)
public override GameObject CreateContent(GameObject parent)
{
try
{
var split = value.ToString().Split(',');
var set = new HashSet<string>();
foreach (var s in split)
set.Add(s.Trim());
UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveEnum", false, false, true, true, 3, new Vector4(4, 4, 4, 4),
new Color(0.06f, 0.06f, 0.06f));
UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleHeight: 9999, flexibleWidth: 9999);
for (int i = 0; i < CurrentValues.Count; i++)
flagToggles[i].isOn = set.Contains(ValueAtIdx(i).Name);
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception setting flag toggles: " + ex);
}
var hori = UIFactory.CreateUIObject("Hori", UIRoot);
UIFactory.SetLayoutElement(hori, minHeight: 25, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(hori, false, false, true, true, 2);
var applyButton = UIFactory.CreateButton(hori, "ApplyButton", "Apply", new Color(0.2f, 0.27f, 0.2f));
UIFactory.SetLayoutElement(applyButton.Component.gameObject, minHeight: 25, minWidth: 100);
applyButton.OnClick += OnApplyClicked;
inputField = UIFactory.CreateInputField(hori, "InputField", "Enter name or underlying value...");
UIFactory.SetLayoutElement(inputField.UIRoot, minHeight: 25, flexibleHeight: 50, minWidth: 100, flexibleWidth: 1000);
inputField.Component.lineType = InputField.LineType.MultiLineNewline;
inputField.UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
enumHelperButton = UIFactory.CreateButton(hori, "EnumHelper", "▼");
UIFactory.SetLayoutElement(enumHelperButton.Component.gameObject, minWidth: 25, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0);
enumHelperButton.OnClick += EnumHelper_OnClick;
enumCompleter = new EnumCompleter(this.EnumType, this.inputField);
toggleHolder = UIFactory.CreateUIObject("ToggleHolder", UIRoot);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(toggleHolder, false, false, true, true, 4);
UIFactory.SetLayoutElement(toggleHolder, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 9999);
return UIRoot;
}
// Setting up the UI for the enum type when it changes or is first set
private void SetupDropdownForEnumType()
{
toggleHolder.SetActive(false);
enumDropdown.gameObject.SetActive(true);
// create dropdown entries
enumDropdown.options.Clear();
foreach (CachedEnumValue entry in CurrentValues.Values)
enumDropdown.options.Add(new Dropdown.OptionData(entry.Name));
enumDropdown.value = 0;
enumDropdown.RefreshShownValue();
}
public CachedEnumValue ValueAtIdx(int idx) => (CachedEnumValue)CurrentValues[idx];
public CachedEnumValue ValueAtKey(object key) => (CachedEnumValue)CurrentValues[key];
private void SetupTogglesForEnumType()
{
toggleHolder.SetActive(true);
enumDropdown.gameObject.SetActive(false);
inputField.Component.gameObject.SetActive(false);
enumHelperButton.Component.gameObject.SetActive(false);
// create / set / hide toggles
for (int i = 0; i < CurrentValues.Count || i < flagToggles.Count; i++)
@ -178,54 +208,13 @@ namespace UnityExplorer.UI.IValues
flagTexts.Add(toggleText);
}
// UI Construction
public override GameObject CreateContent(GameObject parent)
{
UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveEnum", false, false, true, true, 3, new Vector4(4, 4, 4, 4),
new Color(0.06f, 0.06f, 0.06f));
UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleHeight: 9999, flexibleWidth: 9999);
var hori = UIFactory.CreateUIObject("Hori", UIRoot);
UIFactory.SetLayoutElement(hori, minHeight: 25, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(hori, false, false, true, true, 2);
var applyButton = UIFactory.CreateButton(hori, "ApplyButton", "Apply", new Color(0.2f, 0.27f, 0.2f));
UIFactory.SetLayoutElement(applyButton.Component.gameObject, minHeight: 25, minWidth: 100);
applyButton.OnClick += OnApplyClicked;
var dropdownObj = UIFactory.CreateDropdown(hori, out enumDropdown, "not set", 14, null);
UIFactory.SetLayoutElement(dropdownObj, minHeight: 25, flexibleWidth: 600);
toggleHolder = UIFactory.CreateUIObject("ToggleHolder", UIRoot);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(toggleHolder, false, false, true, true, 4);
UIFactory.SetLayoutElement(toggleHolder, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 9999);
return UIRoot;
}
#region Enum cache
public struct CachedEnumValue
{
public CachedEnumValue(object value, int index, string name)
{
EnumIndex = index;
Name = name;
ActualValue = value;
}
public readonly object ActualValue;
public int EnumIndex;
public readonly string Name;
}
internal static readonly Dictionary<string, OrderedDictionary> enumCache = new Dictionary<string, OrderedDictionary>();
internal static OrderedDictionary GetEnumValues(Type enumType, out bool isFlags)
internal static OrderedDictionary GetEnumValues(Type enumType)
{
isFlags = enumType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] fa && fa.Any();
//isFlags = enumType.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] fa && fa.Any();
if (!enumCache.ContainsKey(enumType.AssemblyQualifiedName))
{
@ -252,4 +241,18 @@ namespace UnityExplorer.UI.IValues
#endregion
}
public struct CachedEnumValue
{
public CachedEnumValue(object value, int index, string name)
{
EnumIndex = index;
Name = name;
ActualValue = value;
}
public readonly object ActualValue;
public int EnumIndex;
public readonly string Name;
}
}

View File

@ -5,14 +5,19 @@ using System.Linq;
using System.Reflection;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.CacheObject;
using UnityExplorer.UI.CacheObject.Views;
using UnityExplorer.UI.Inspectors;
using UnityExplorer.CacheObject;
using UnityExplorer.CacheObject.Views;
using UnityExplorer.Inspectors;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Utility;
using UnityExplorer.UI.Widgets;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Widgets;
using UniverseLib.UI.Widgets.ScrollView;
using UniverseLib.Utility;
namespace UnityExplorer.UI.IValues
namespace UnityExplorer.CacheObject.IValues
{
public class InteractiveList : InteractiveValue, ICellPoolDataSource<CacheListEntryCell>, ICacheObjectController
{
@ -29,11 +34,13 @@ namespace UnityExplorer.UI.IValues
private PropertyInfo genericIndexer;
public int ItemCount => cachedEntries.Count;
private readonly List<CacheListEntry> cachedEntries = new List<CacheListEntry>();
private readonly List<CacheListEntry> cachedEntries = new();
public ScrollPool<CacheListEntryCell> ListScrollPool { get; private set; }
public Text TopLabel;
private LayoutElement scrollLayout;
private Text NotSupportedLabel;
public override void OnBorrowed(CacheObjectBase owner)
{
@ -62,6 +69,28 @@ namespace UnityExplorer.UI.IValues
cachedEntries.Clear();
}
// List entry scroll pool
public override void SetLayout()
{
var minHeight = 5f;
foreach (var cell in ListScrollPool.CellPool)
{
if (cell.Enabled)
minHeight += cell.Rect.rect.height;
}
this.scrollLayout.minHeight = Math.Min(InspectorPanel.CurrentPanelHeight - 400f, minHeight);
}
public void OnCellBorrowed(CacheListEntryCell cell) { } // not needed
public void SetCell(CacheListEntryCell cell, int index)
{
CacheObjectControllerHelper.SetCell(cell, index, cachedEntries, null);
}
// Setting the List value itself to this model
public override void SetValue(object value)
{
@ -207,34 +236,8 @@ namespace UnityExplorer.UI.IValues
{
ExplorerCore.LogWarning($"Exception setting IList value: {ex}");
}
}
// List entry scroll pool
public override void SetLayout()
{
var minHeight = 5f;
foreach (var cell in ListScrollPool.CellPool)
{
if (cell.Enabled)
minHeight += cell.Rect.rect.height;
}
this.scrollLayout.minHeight = Math.Min(InspectorPanel.CurrentPanelHeight - 400f, minHeight);
}
public void OnCellBorrowed(CacheListEntryCell cell) { } // not needed
public void SetCell(CacheListEntryCell cell, int index)
{
CacheObjectControllerHelper.SetCell(cell, index, cachedEntries, null);
}
private LayoutElement scrollLayout;
private Text NotSupportedLabel;
public override GameObject CreateContent(GameObject parent)
{
UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveList", true, true, true, true, 6, new Vector4(10, 3, 15, 4),
@ -255,8 +258,8 @@ namespace UnityExplorer.UI.IValues
ListScrollPool.Initialize(this, SetLayout);
scrollLayout = scrollObj.GetComponent<LayoutElement>();
NotSupportedLabel = UIFactory.CreateLabel(ListScrollPool.Content.gameObject, "NotSupportedMessage",
"The IEnumerable failed to enumerate. This is likely due to an issue with Unhollowed interfaces.",
NotSupportedLabel = UIFactory.CreateLabel(ListScrollPool.Content.gameObject, "NotSupportedMessage",
"The IEnumerable failed to enumerate. This is likely due to an issue with Unhollowed interfaces.",
TextAnchor.MiddleLeft, Color.red);
UIFactory.SetLayoutElement(NotSupportedLabel.gameObject, minHeight: 25, flexibleWidth: 9999);

View File

@ -5,11 +5,16 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Config;
using UnityExplorer.UI.CacheObject;
using UnityExplorer.Config;
using UnityExplorer.CacheObject;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI;
using UniverseLib.UI;
using UniverseLib;
using UniverseLib.UI.Models;
using UniverseLib.Utility;
namespace UnityExplorer.UI.IValues
namespace UnityExplorer.CacheObject.IValues
{
public class InteractiveString : InteractiveValue
{
@ -18,7 +23,7 @@ namespace UnityExplorer.UI.IValues
public InputFieldRef inputField;
public ButtonRef ApplyButton;
public GameObject SaveFileRow;
public InputFieldRef SaveFilePath;
@ -36,8 +41,8 @@ namespace UnityExplorer.UI.IValues
{
if (s == null)
return false;
return s.Length >= UIManager.MAX_INPUTFIELD_CHARS;
return s.Length >= UniversalUI.MAX_INPUTFIELD_CHARS;
}
public override void SetValue(object value)
@ -79,11 +84,11 @@ namespace UnityExplorer.UI.IValues
return;
}
var path = IOUtility.EnsureValidDirectory(SaveFilePath.Text);
var path = IOUtility.EnsureValidFilePath(SaveFilePath.Text);
if (File.Exists(path))
File.Delete(path);
File.WriteAllText(path, RealValue);
}
@ -98,7 +103,7 @@ namespace UnityExplorer.UI.IValues
UIFactory.SetLayoutElement(SaveFileRow, flexibleWidth: 9999);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(SaveFileRow, false, true, true, true, 3);
UIFactory.CreateLabel(SaveFileRow, "Info", "<color=red>String is too long! Save to file if you want to see the full string.</color>",
UIFactory.CreateLabel(SaveFileRow, "Info", "<color=red>String is too long! Save to file if you want to see the full string.</color>",
TextAnchor.MiddleLeft);
var horizRow = UIFactory.CreateUIObject("Horiz", SaveFileRow);

View File

@ -4,10 +4,13 @@ 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 UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.ObjectPool;
namespace UnityExplorer.UI.IValues
namespace UnityExplorer.CacheObject.IValues
{
public abstract class InteractiveValue : IPooledObject
{

View File

@ -5,10 +5,14 @@ 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;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.Utility;
namespace UnityExplorer.UI.IValues
namespace UnityExplorer.CacheObject.IValues
{
public class InteractiveValueStruct : InteractiveValue
{
@ -53,7 +57,7 @@ namespace UnityExplorer.UI.IValues
}
}
private static readonly Dictionary<string, StructInfo> typeSupportCache = new Dictionary<string, StructInfo>();
private static readonly Dictionary<string, StructInfo> typeSupportCache = new();
private const BindingFlags INSTANCE_FLAGS = BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public;
private const string SYSTEM_VOID = "System.Void";
@ -144,7 +148,7 @@ namespace UnityExplorer.UI.IValues
ExplorerCore.LogWarning("Exception setting value: " + ex);
}
}
// UI Setup for type
private void SetupUIForType()

View File

@ -4,8 +4,12 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.Utility;
namespace UnityExplorer.UI.CacheObject.Views
namespace UnityExplorer.CacheObject.Views
{
public class ConfigEntryCell : CacheObjectCell
{

View File

@ -4,11 +4,14 @@ 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;
using UniverseLib.UI;
using UniverseLib.UI.Models;
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,21 +4,20 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets;
using UniverseLib.UI;
using UniverseLib.UI.Models;
namespace UnityExplorer.UI.CacheObject.Views
namespace UnityExplorer.CacheObject.Views
{
public class CacheMemberCell : CacheObjectCell
{
//public ReflectionInspector Owner { get; set; }
public CacheMember MemberOccupant => Occupant as CacheMember;
public GameObject EvaluateHolder;
public ButtonRef EvaluateButton;
//public Toggle UpdateToggle;
protected virtual void EvaluateClicked()
{
this.MemberOccupant.OnEvaluateClicked();
@ -36,16 +35,5 @@ namespace UnityExplorer.UI.CacheObject.Views
UIFactory.SetLayoutElement(EvaluateButton.Component.gameObject, minWidth: 100, minHeight: 25);
EvaluateButton.OnClick += EvaluateClicked;
}
//protected override void ConstructUpdateToggle(GameObject parent)
//{
// // Auto-update toggle
//
// var updateToggle = UIFactory.CreateToggle(parent, "AutoUpdate", out UpdateToggle, out Text autoText);
// UIFactory.SetLayoutElement(updateToggle, minHeight: 25, minWidth: 30, flexibleWidth: 0, flexibleHeight: 0);
// GameObject.Destroy(autoText);
// UpdateToggle.isOn = false;
// UpdateToggle.onValueChanged.AddListener((bool val) => { MemberOccupant.AutoUpdateWanted = val; });
//}
}
}

View File

@ -4,13 +4,14 @@ 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.UI.Widgets;
using UnityExplorer.UI.Panels;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets.ScrollView;
using UniverseLib.Utility;
namespace UnityExplorer.UI.CacheObject.Views
namespace UnityExplorer.CacheObject.Views
{
public abstract class CacheObjectCell : ICell
{
@ -43,9 +44,12 @@ namespace UnityExplorer.UI.CacheObject.Views
public bool SubContentActive => SubContentHolder.activeSelf;
public LayoutElement NameLayout;
public GameObject RightGroupContent;
public LayoutElement RightGroupLayout;
public GameObject SubContentHolder;
public Text NameLabel;
public InputFieldRef HiddenNameLabel; // for selecting the name label
public Text TypeLabel;
public Text ValueLabel;
public Toggle Toggle;
@ -56,7 +60,11 @@ namespace UnityExplorer.UI.CacheObject.Views
public ButtonRef SubContentButton;
public ButtonRef ApplyButton;
public GameObject SubContentHolder;
public ButtonRef CopyButton;
public ButtonRef PasteButton;
public readonly Color subInactiveColor = new(0.23f, 0.23f, 0.23f);
public readonly Color subActiveColor = new(0.23f, 0.33f, 0.23f);
protected virtual void ApplyClicked()
{
@ -78,26 +86,26 @@ namespace UnityExplorer.UI.CacheObject.Views
this.Occupant.OnCellSubContentToggle();
}
public readonly Color subInactiveColor = new Color(0.23f, 0.23f, 0.23f);
public readonly Color subActiveColor = new Color(0.23f, 0.33f, 0.23f);
protected virtual void OnCopyClicked()
{
ClipboardPanel.Copy(this.Occupant.Value);
}
protected virtual void OnPasteClicked()
{
if (ClipboardPanel.TryPaste(this.Occupant.FallbackType, out object paste))
this.Occupant.SetUserValue(paste);
}
public void RefreshSubcontentButton()
{
if (!this.SubContentHolder.activeSelf)
{
this.SubContentButton.ButtonText.text = "▲";
RuntimeProvider.Instance.SetColorBlock(SubContentButton.Component, subInactiveColor, subInactiveColor * 1.3f);
}
else
{
this.SubContentButton.ButtonText.text = "▼";
RuntimeProvider.Instance.SetColorBlock(SubContentButton.Component, subActiveColor, subActiveColor * 1.3f);
}
this.SubContentButton.ButtonText.text = SubContentHolder.activeSelf ? "▼" : "▲";
Color color = SubContentHolder.activeSelf ? subActiveColor : subInactiveColor;
RuntimeHelper.SetColorBlock(SubContentButton.Component, color, color * 1.3f);
}
protected abstract void ConstructEvaluateHolder(GameObject parent);
public virtual GameObject CreateContent(GameObject parent)
{
// Main layout
@ -117,21 +125,32 @@ namespace UnityExplorer.UI.CacheObject.Views
NameLabel = UIFactory.CreateLabel(horiRow, "NameLabel", "<notset>", TextAnchor.MiddleLeft);
NameLabel.horizontalOverflow = HorizontalWrapMode.Wrap;
UIFactory.SetLayoutElement(NameLabel.gameObject, minHeight: 25, minWidth: 20, flexibleHeight: 300, flexibleWidth: 0);
NameLayout = NameLabel.GetComponent<LayoutElement>();
NameLayout = UIFactory.SetLayoutElement(NameLabel.gameObject, minHeight: 25, minWidth: 20, flexibleHeight: 300, flexibleWidth: 0);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(NameLabel.gameObject, true, true, true, true);
HiddenNameLabel = UIFactory.CreateInputField(NameLabel.gameObject, "HiddenNameLabel", "");
var hiddenRect = HiddenNameLabel.Component.GetComponent<RectTransform>();
hiddenRect.anchorMin = Vector2.zero;
hiddenRect.anchorMax = Vector2.one;
HiddenNameLabel.Component.readOnly = true;
HiddenNameLabel.Component.lineType = UnityEngine.UI.InputField.LineType.MultiLineNewline;
HiddenNameLabel.Component.textComponent.horizontalOverflow = HorizontalWrapMode.Wrap;
HiddenNameLabel.Component.gameObject.GetComponent<Image>().color = Color.clear;
HiddenNameLabel.Component.textComponent.color = Color.clear;
UIFactory.SetLayoutElement(HiddenNameLabel.Component.gameObject, minHeight: 25, minWidth: 20, flexibleHeight: 300, flexibleWidth: 0);
// Right vertical group
var rightGroupHolder = UIFactory.CreateUIObject("RightGroup", horiRow);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(rightGroupHolder, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft);
UIFactory.SetLayoutElement(rightGroupHolder, minHeight: 25, minWidth: 200, flexibleWidth: 9999, flexibleHeight: 800);
RightGroupLayout = rightGroupHolder.GetComponent<LayoutElement>();
RightGroupContent = UIFactory.CreateUIObject("RightGroup", horiRow);
UIFactory.SetLayoutGroup<VerticalLayoutGroup>(RightGroupContent, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft);
UIFactory.SetLayoutElement(RightGroupContent, minHeight: 25, minWidth: 200, flexibleWidth: 9999, flexibleHeight: 800);
RightGroupLayout = RightGroupContent.GetComponent<LayoutElement>();
ConstructEvaluateHolder(rightGroupHolder);
ConstructEvaluateHolder(RightGroupContent);
// Right horizontal group
var rightHoriGroup = UIFactory.CreateUIObject("RightHoriGroup", rightGroupHolder);
var rightHoriGroup = UIFactory.CreateUIObject("RightHoriGroup", RightGroupContent);
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(rightHoriGroup, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft);
UIFactory.SetLayoutElement(rightHoriGroup, minHeight: 25, minWidth: 200, flexibleWidth: 9999, flexibleHeight: 800);
@ -143,7 +162,7 @@ namespace UnityExplorer.UI.CacheObject.Views
TypeLabel = UIFactory.CreateLabel(rightHoriGroup, "ReturnLabel", "<notset>", TextAnchor.MiddleLeft);
TypeLabel.horizontalOverflow = HorizontalWrapMode.Wrap;
UIFactory.SetLayoutElement(TypeLabel.gameObject, minHeight: 25, flexibleHeight: 150, minWidth: 60, flexibleWidth: 0);
UIFactory.SetLayoutElement(TypeLabel.gameObject, minHeight: 25, flexibleHeight: 150, minWidth: 45, flexibleWidth: 0);
// Bool and number value interaction
@ -173,6 +192,24 @@ namespace UnityExplorer.UI.CacheObject.Views
ValueLabel.horizontalOverflow = HorizontalWrapMode.Wrap;
UIFactory.SetLayoutElement(ValueLabel.gameObject, minHeight: 25, flexibleHeight: 150, flexibleWidth: 9999);
// Copy and Paste buttons
var buttonHolder = UIFactory.CreateHorizontalGroup(rightHoriGroup, "CopyPasteButtons", false, false, true, true, 4,
bgColor: new(1,1,1,0), childAlignment: TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(buttonHolder, minWidth: 60, flexibleWidth: 0);
CopyButton = UIFactory.CreateButton(buttonHolder, "CopyButton", "Copy", new Color(0.13f, 0.13f, 0.13f, 1f));
UIFactory.SetLayoutElement(CopyButton.Component.gameObject, minHeight: 25, minWidth: 28, flexibleWidth: 0);
CopyButton.ButtonText.color = Color.yellow;
CopyButton.ButtonText.fontSize = 10;
CopyButton.OnClick += OnCopyClicked;
PasteButton = UIFactory.CreateButton(buttonHolder, "PasteButton", "Paste", new Color(0.13f, 0.13f, 0.13f, 1f));
UIFactory.SetLayoutElement(PasteButton.Component.gameObject, minHeight: 25, minWidth: 28, flexibleWidth: 0);
PasteButton.ButtonText.color = Color.green;
PasteButton.ButtonText.fontSize = 10;
PasteButton.OnClick += OnPasteClicked;
// Subcontent
SubContentHolder = UIFactory.CreateUIObject("SubContent", UIRoot);

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer.Core.Config
namespace UnityExplorer.Config
{
public class ConfigElement<T> : IConfigElement
{

View File

@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer.Core.Config
namespace UnityExplorer.Config
{
public abstract class ConfigHandler
{

View File

@ -8,37 +8,41 @@ using System.Text;
using UnityEngine;
using UnityExplorer.UI;
namespace UnityExplorer.Core.Config
namespace UnityExplorer.Config
{
public static class ConfigManager
{
internal static readonly Dictionary<string, IConfigElement> ConfigElements = new();
internal static readonly Dictionary<string, IConfigElement> InternalConfigs = new();
// Each Mod Loader has its own ConfigHandler.
// See the UnityExplorer.Loader namespace for the implementations.
public static ConfigHandler Handler { get; private set; }
public static ConfigElement<KeyCode> Master_Toggle;
// Actual UE Settings
public static ConfigElement<KeyCode> Master_Toggle;
public static ConfigElement<int> Target_Display;
public static ConfigElement<UIManager.VerticalAnchor> Main_Navbar_Anchor;
public static ConfigElement<bool> Force_Unlock_Mouse;
public static ConfigElement<KeyCode> Force_Unlock_Toggle;
public static ConfigElement<bool> Aggressive_Mouse_Unlock;
public static ConfigElement<string> Default_Output_Path;
public static ConfigElement<bool> Log_Unity_Debug;
public static ConfigElement<bool> Hide_On_Startup;
public static ConfigElement<float> Startup_Delay_Time;
public static ConfigElement<string> Reflection_Signature_Blacklist;
public static ConfigElement<bool> Force_Unlock_Mouse;
public static ConfigElement<KeyCode> Force_Unlock_Toggle;
public static ConfigElement<bool> Aggressive_Mouse_Unlock;
public static ConfigElement<bool> Disable_EventSystem_Override;
public static ConfigElement<string> Default_Output_Path;
public static ConfigElement<bool> Log_Unity_Debug;
public static ConfigElement<bool> Hide_On_Startup;
public static ConfigElement<float> Startup_Delay_Time;
public static ConfigElement<string> Reflection_Signature_Blacklist;
// internal configs
internal static InternalConfigHandler InternalHandler { get; private set; }
internal static readonly Dictionary<UIManager.Panels, ConfigElement<string>> PanelSaveData = new();
public static ConfigElement<string> ObjectExplorerData;
public static ConfigElement<string> InspectorData;
public static ConfigElement<string> CSConsoleData;
public static ConfigElement<string> OptionsPanelData;
public static ConfigElement<string> ConsoleLogData;
internal static readonly Dictionary<string, IConfigElement> ConfigElements = new Dictionary<string, IConfigElement>();
internal static readonly Dictionary<string, IConfigElement> InternalConfigs = new Dictionary<string, IConfigElement>();
internal static ConfigElement<string> GetPanelSaveData(UIManager.Panels panel)
{
if (!PanelSaveData.ContainsKey(panel))
PanelSaveData.Add(panel, new ConfigElement<string>(panel.ToString(), string.Empty, string.Empty, true));
return PanelSaveData[panel];
}
public static void Init(ConfigHandler configHandler)
{
@ -76,6 +80,11 @@ namespace UnityExplorer.Core.Config
"The key to enable or disable UnityExplorer's menu and features.",
KeyCode.F7);
Target_Display = new ConfigElement<int>("Target Display",
"The monitor index for UnityExplorer to use, if you have multiple. 0 is the default display, 1 is secondary, etc. " +
"Restart recommended when changing this setting. Make sure your extra monitors are the same resolution as your primary monitor.",
0);
Main_Navbar_Anchor = new ConfigElement<UIManager.VerticalAnchor>("Main Navbar Anchor",
"The vertical anchor of the main UnityExplorer Navbar, in case you want to move it.",
UIManager.VerticalAnchor.Top);
@ -87,14 +96,22 @@ namespace UnityExplorer.Core.Config
Force_Unlock_Mouse = new ConfigElement<bool>("Force Unlock Mouse",
"Force the Cursor to be unlocked (visible) when the UnityExplorer menu is open.",
true);
Force_Unlock_Mouse.OnValueChanged += (bool value) =>
{
UniverseLib.Config.ConfigManager.Force_Unlock_Mouse = value;
};
Force_Unlock_Toggle = new ConfigElement<KeyCode>("Force Unlock Toggle Key",
"The keybind to toggle the 'Force Unlock Mouse' setting. Only usable when UnityExplorer is open.",
KeyCode.None);
Aggressive_Mouse_Unlock = new ConfigElement<bool>("Aggressive Mouse Unlock",
"Use WaitForEndOfFrame to aggressively force the Mouse to be unlocked (requires game restart).",
Disable_EventSystem_Override = new ConfigElement<bool>("Disable EventSystem override",
"If enabled, UnityExplorer will not override the EventSystem from the game.\n<b>May require restart to take effect.</b>",
false);
Disable_EventSystem_Override.OnValueChanged += (bool value) =>
{
UniverseLib.Config.ConfigManager.Disable_EventSystem_Override = value;
};
Log_Unity_Debug = new ConfigElement<bool>("Log Unity Debug",
"Should UnityEngine.Debug.Log messages be printed to UnityExplorer's log?",
@ -108,18 +125,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");
// Internal configs (panel save data)
ObjectExplorerData = new ConfigElement<string>("ObjectExplorer", "", "", true);
InspectorData = new ConfigElement<string>("Inspector", "", "", true);
CSConsoleData = new ConfigElement<string>("CSConsole", "", "", true);
OptionsPanelData = new ConfigElement<string>("OptionsPanel", "", "", true);
ConsoleLogData = new ConfigElement<string>("ConsoleLog", "", "", true);
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;'",
"");
}
}
}

View File

@ -1,6 +1,6 @@
using System;
namespace UnityExplorer.Core.Config
namespace UnityExplorer.Config
{
public interface IConfigElement
{

View File

@ -0,0 +1,79 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityExplorer.UI;
using Tomlet;
using Tomlet.Models;
namespace UnityExplorer.Config
{
public class InternalConfigHandler : ConfigHandler
{
internal static string CONFIG_PATH;
public override void Init()
{
CONFIG_PATH = Path.Combine(ExplorerCore.Loader.ExplorerFolder, "data.cfg");
}
public override void LoadConfig()
{
if (!TryLoadConfig())
SaveConfig();
}
public override void RegisterConfigElement<T>(ConfigElement<T> element)
{
// Not necessary
}
public override void SetConfigValue<T>(ConfigElement<T> element, T value)
{
// Not necessary
}
// Not necessary, just return the value.
public override T GetConfigValue<T>(ConfigElement<T> element) => element.Value;
// Always just auto-save.
public override void OnAnyConfigChanged() => SaveConfig();
public bool TryLoadConfig()
{
try
{
if (!File.Exists(CONFIG_PATH))
return false;
TomlDocument document = TomlParser.ParseFile(CONFIG_PATH);
foreach (var key in document.Keys)
{
var panelKey = (UIManager.Panels)Enum.Parse(typeof(UIManager.Panels), key);
ConfigManager.GetPanelSaveData(panelKey).Value = document.GetString(key);
}
return true;
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Error loading internal data: " + ex.ToString());
return false;
}
}
public override void SaveConfig()
{
if (UIManager.Initializing)
return;
var tomlDocument = TomlDocument.CreateEmpty();
foreach (var entry in ConfigManager.InternalConfigs)
tomlDocument.Put(entry.Key, entry.Value.BoxedValue as string, false);
File.WriteAllText(CONFIG_PATH, tomlDocument.SerializedValue);
}
}
}

View File

@ -1,108 +0,0 @@
using IniParser.Parser;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityExplorer.UI;
namespace UnityExplorer.Core.Config
{
public class InternalConfigHandler : ConfigHandler
{
internal static IniDataParser _parser;
internal static string INI_PATH;
public override void Init()
{
INI_PATH = Path.Combine(ExplorerCore.Loader.ExplorerFolder, "data.ini");
_parser = new IniDataParser();
_parser.Configuration.CommentString = "#";
}
public override void LoadConfig()
{
if (!TryLoadConfig())
SaveConfig();
}
public override void RegisterConfigElement<T>(ConfigElement<T> element)
{
// Not necessary
}
public override void SetConfigValue<T>(ConfigElement<T> element, T value)
{
// Not necessary
}
public override T GetConfigValue<T>(ConfigElement<T> element)
{
// Not necessary, just return the value.
return element.Value;
}
public override void OnAnyConfigChanged()
{
SaveConfig();
}
public bool TryLoadConfig()
{
try
{
if (!File.Exists(INI_PATH))
return false;
string ini = File.ReadAllText(INI_PATH);
var data = _parser.Parse(ini);
foreach (var config in data.Sections["Config"])
{
if (ConfigManager.InternalConfigs.TryGetValue(config.KeyName, out IConfigElement configElement))
configElement.BoxedValue = StringToConfigValue(config.Value, configElement.ElementType);
}
return true;
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Error loading internal data: " + ex.ToString());
return false;
}
}
public override void SaveConfig()
{
if (UIManager.Initializing)
return;
var data = new IniParser.Model.IniData();
data.Sections.AddSection("Config");
var sec = data.Sections["Config"];
foreach (var entry in ConfigManager.InternalConfigs)
sec.AddKey(entry.Key, entry.Value.BoxedValue.ToString());
if (!Directory.Exists(ExplorerCore.Loader.ConfigFolder))
Directory.CreateDirectory(ExplorerCore.Loader.ConfigFolder);
File.WriteAllText(INI_PATH, data.ToString());
}
public object StringToConfigValue(string value, Type elementType)
{
if (elementType.IsEnum)
return Enum.Parse(elementType, value);
else if (elementType == typeof(bool))
return bool.Parse(value);
else if (elementType == typeof(int))
return int.Parse(value);
else
return value;
}
}
}

View File

@ -1,212 +0,0 @@
using System;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer.Core.Input;
using BF = System.Reflection.BindingFlags;
using UnityExplorer.Core.Config;
using UnityExplorer.Core;
using UnityExplorer.UI;
using System.Collections;
using HarmonyLib;
namespace UnityExplorer.Core.Input
{
public class CursorUnlocker
{
public static bool Unlock
{
get => m_forceUnlock;
set
{
m_forceUnlock = value;
UpdateCursorControl();
}
}
private static bool m_forceUnlock;
public static bool ShouldActuallyUnlock => UIManager.ShowMenu && Unlock;
private static CursorLockMode lastLockMode;
private static bool lastVisibleState;
private static bool currentlySettingCursor = false;
public static void Init()
{
lastLockMode = Cursor.lockState;
lastVisibleState = Cursor.visible;
SetupPatches();
UpdateCursorControl();
Unlock = ConfigManager.Force_Unlock_Mouse.Value;
ConfigManager.Force_Unlock_Mouse.OnValueChanged += (bool val) => { Unlock = val; };
if (ConfigManager.Aggressive_Mouse_Unlock.Value)
SetupAggressiveUnlock();
}
public static void SetupAggressiveUnlock()
{
try
{
RuntimeProvider.Instance.StartCoroutine(AggressiveUnlockCoroutine());
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception setting up Aggressive Mouse Unlock: {ex}");
}
}
private static readonly WaitForEndOfFrame _waitForEndOfFrame = new WaitForEndOfFrame();
private static IEnumerator AggressiveUnlockCoroutine()
{
while (true)
{
yield return _waitForEndOfFrame;
if (UIManager.ShowMenu)
UpdateCursorControl();
}
}
public static void UpdateCursorControl()
{
try
{
currentlySettingCursor = true;
if (ShouldActuallyUnlock)
{
Cursor.lockState = CursorLockMode.None;
Cursor.visible = true;
if (UIManager.EventSys)
SetEventSystem();
}
else
{
Cursor.lockState = lastLockMode;
Cursor.visible = lastVisibleState;
if (UIManager.EventSys)
ReleaseEventSystem();
}
currentlySettingCursor = false;
}
catch (Exception e)
{
ExplorerCore.Log($"Exception setting Cursor state: {e.GetType()}, {e.Message}");
}
}
// Event system overrides
private static bool settingEventSystem;
private static EventSystem lastEventSystem;
private static BaseInputModule lastInputModule;
public static void SetEventSystem()
{
if (InputManager.CurrentType == InputType.InputSystem)
return;
if (EventSystem.current && EventSystem.current != UIManager.EventSys)
{
lastEventSystem = EventSystem.current;
lastEventSystem.enabled = false;
}
// Set to our current system
settingEventSystem = true;
UIManager.EventSys.enabled = true;
EventSystem.current = UIManager.EventSys;
InputManager.ActivateUIModule();
settingEventSystem = false;
}
public static void ReleaseEventSystem()
{
if (InputManager.CurrentType == InputType.InputSystem)
return;
if (lastEventSystem && lastEventSystem.gameObject.activeSelf)
{
lastEventSystem.enabled = true;
settingEventSystem = true;
EventSystem.current = lastEventSystem;
lastInputModule?.ActivateModule();
settingEventSystem = false;
}
}
// Patches
private static void SetupPatches()
{
try
{
ExplorerCore.Loader.SetupCursorPatches();
}
catch (Exception e)
{
ExplorerCore.Log($"Exception setting up Cursor patches: {e.GetType()}, {e.Message}");
}
}
public static void Prefix_EventSystem_set_current(ref EventSystem value)
{
if (!UIManager.EventSys)
{
if (value)
{
lastEventSystem = value;
lastInputModule = value.currentInputModule;
}
return;
}
if (!settingEventSystem && value != UIManager.EventSys)
{
lastEventSystem = value;
lastInputModule = value?.currentInputModule;
if (ShouldActuallyUnlock)
{
value = UIManager.EventSys;
value.enabled = true;
}
}
}
// Force mouse to stay unlocked and visible while UnlockMouse and ShowMenu are true.
// Also keep track of when anything else tries to set Cursor state, this will be the
// value that we set back to when we close the menu or disable force-unlock.
public static void Prefix_set_lockState(ref CursorLockMode value)
{
if (!currentlySettingCursor)
{
lastLockMode = value;
if (ShouldActuallyUnlock)
value = CursorLockMode.None;
}
}
public static void Prefix_set_visible(ref bool value)
{
if (!currentlySettingCursor)
{
lastVisibleState = value;
if (ShouldActuallyUnlock)
value = true;
}
}
}
}

View File

@ -1,22 +0,0 @@
using UnityEngine;
using UnityEngine.EventSystems;
namespace UnityExplorer.Core.Input
{
public interface IHandleInput
{
Vector2 MousePosition { get; }
Vector2 MouseScrollDelta { get; }
bool GetKeyDown(KeyCode key);
bool GetKey(KeyCode key);
bool GetMouseButtonDown(int btn);
bool GetMouseButton(int btn);
BaseInputModule UIModule { get; }
void AddUIInputModule();
void ActivateModule();
}
}

View File

@ -1,103 +0,0 @@
using System;
using UnityEngine;
using System.Diagnostics.CodeAnalysis;
using UnityEngine.EventSystems;
namespace UnityExplorer.Core.Input
{
public enum InputType
{
InputSystem,
Legacy,
None
}
public static class InputManager
{
public static InputType CurrentType { get; private set; }
private static IHandleInput m_inputModule;
public static Vector3 MousePosition => m_inputModule.MousePosition;
public static bool GetKeyDown(KeyCode key)
{
if (key == KeyCode.None)
return false;
return m_inputModule.GetKeyDown(key);
}
public static bool GetKey(KeyCode key)
{
if (key == KeyCode.None)
return false;
return m_inputModule.GetKey(key);
}
public static bool GetMouseButtonDown(int btn) => m_inputModule.GetMouseButtonDown(btn);
public static bool GetMouseButton(int btn) => m_inputModule.GetMouseButton(btn);
public static BaseInputModule UIInput => m_inputModule.UIModule;
public static Vector2 MouseScrollDelta => m_inputModule.MouseScrollDelta;
public static void ActivateUIModule() => m_inputModule.ActivateModule();
public static void AddUIModule()
{
m_inputModule.AddUIInputModule();
ActivateUIModule();
}
public static void Init()
{
InitHandler();
CursorUnlocker.Init();
}
private static void InitHandler()
{
// First, just try to use the legacy input, see if its working.
// The InputSystem package may be present but not actually activated, so we can find out this way.
if (LegacyInput.TInput != null)
{
try
{
m_inputModule = new LegacyInput();
CurrentType = InputType.Legacy;
// make sure its working
GetKeyDown(KeyCode.F5);
ExplorerCore.Log("Initialized Legacy Input support");
return;
}
catch
{
// It's not working, we'll fall back to InputSystem.
}
}
if (InputSystem.TKeyboard != null)
{
try
{
m_inputModule = new InputSystem();
CurrentType = InputType.InputSystem;
ExplorerCore.Log("Initialized new InputSystem support.");
return;
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
}
ExplorerCore.LogWarning("Could not find any Input Module Type!");
m_inputModule = new NoInput();
CurrentType = InputType.None;
}
}
}

View File

@ -1,233 +0,0 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer.UI;
using System.Collections.Generic;
using System.Linq;
namespace UnityExplorer.Core.Input
{
public class InputSystem : IHandleInput
{
public InputSystem()
{
m_kbCurrentProp = TKeyboard.GetProperty("current");
m_kbIndexer = TKeyboard.GetProperty("Item", new Type[] { TKey });
var btnControl = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Controls.ButtonControl");
m_btnIsPressedProp = btnControl.GetProperty("isPressed");
m_btnWasPressedProp = btnControl.GetProperty("wasPressedThisFrame");
m_mouseCurrentProp = TMouse.GetProperty("current");
m_leftButtonProp = TMouse.GetProperty("leftButton");
m_rightButtonProp = TMouse.GetProperty("rightButton");
m_scrollDeltaProp = TMouse.GetProperty("scroll");
m_positionProp = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Pointer")
.GetProperty("position");
ReadV2ControlMethod = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputControl`1")
.MakeGenericType(typeof(Vector2))
.GetMethod("ReadValue");
}
#region reflection cache
public static Type TKeyboard => m_tKeyboard ?? (m_tKeyboard = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Keyboard"));
private static Type m_tKeyboard;
public static Type TMouse => m_tMouse ?? (m_tMouse = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Mouse"));
private static Type m_tMouse;
public static Type TKey => m_tKey ?? (m_tKey = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.Key"));
private static Type m_tKey;
private static PropertyInfo m_btnIsPressedProp;
private static PropertyInfo m_btnWasPressedProp;
private static object CurrentKeyboard => m_currentKeyboard ?? (m_currentKeyboard = m_kbCurrentProp.GetValue(null, null));
private static object m_currentKeyboard;
private static PropertyInfo m_kbCurrentProp;
private static PropertyInfo m_kbIndexer;
private static object CurrentMouse => m_currentMouse ?? (m_currentMouse = m_mouseCurrentProp.GetValue(null, null));
private static object m_currentMouse;
private static PropertyInfo m_mouseCurrentProp;
private static object LeftMouseButton => m_lmb ?? (m_lmb = m_leftButtonProp.GetValue(CurrentMouse, null));
private static object m_lmb;
private static PropertyInfo m_leftButtonProp;
private static object RightMouseButton => m_rmb ?? (m_rmb = m_rightButtonProp.GetValue(CurrentMouse, null));
private static object m_rmb;
private static PropertyInfo m_rightButtonProp;
private static MethodInfo ReadV2ControlMethod;
private static object MousePositionInfo => m_pos ?? (m_pos = m_positionProp.GetValue(CurrentMouse, null));
private static object m_pos;
private static PropertyInfo m_positionProp;
private static object MouseScrollInfo => m_scrollInfo ?? (m_scrollInfo = m_scrollDeltaProp.GetValue(CurrentMouse, null));
private static object m_scrollInfo;
private static PropertyInfo m_scrollDeltaProp;
#endregion
public Vector2 MousePosition
{
get
{
try
{
return (Vector2)ReadV2ControlMethod.Invoke(MousePositionInfo, ArgumentUtility.EmptyArgs);
}
catch { return Vector2.zero; }
}
}
public Vector2 MouseScrollDelta
{
get
{
try
{
return (Vector2)ReadV2ControlMethod.Invoke(MouseScrollInfo, ArgumentUtility.EmptyArgs);
}
catch { return Vector2.zero; }
}
}
internal static Dictionary<KeyCode, object> ActualKeyDict = new Dictionary<KeyCode, object>();
internal static Dictionary<string, string> enumNameFixes = new Dictionary<string, string>
{
{ "Control", "Ctrl" },
{ "Return", "Enter" },
{ "Alpha", "Digit" },
{ "Keypad", "Numpad" },
{ "Numlock", "NumLock" },
{ "Print", "PrintScreen" },
{ "BackQuote", "Backquote" }
};
internal object GetActualKey(KeyCode key)
{
if (!ActualKeyDict.ContainsKey(key))
{
var s = key.ToString();
try
{
if (enumNameFixes.First(it => s.Contains(it.Key)) is KeyValuePair<string, string> entry)
s = s.Replace(entry.Key, entry.Value);
}
catch { }
var parsed = Enum.Parse(TKey, s);
var actualKey = m_kbIndexer.GetValue(CurrentKeyboard, new object[] { parsed });
ActualKeyDict.Add(key, actualKey);
}
return ActualKeyDict[key];
}
public bool GetKeyDown(KeyCode key) => (bool)m_btnWasPressedProp.GetValue(GetActualKey(key), null);
public bool GetKey(KeyCode key) => (bool)m_btnIsPressedProp.GetValue(GetActualKey(key), null);
public bool GetMouseButtonDown(int btn)
{
switch (btn)
{
case 0: return (bool)m_btnWasPressedProp.GetValue(LeftMouseButton, null);
case 1: return (bool)m_btnWasPressedProp.GetValue(RightMouseButton, null);
// case 2: return (bool)_btnWasPressedProp.GetValue(MiddleMouseButton, null);
default: throw new NotImplementedException();
}
}
public bool GetMouseButton(int btn)
{
switch (btn)
{
case 0: return (bool)m_btnIsPressedProp.GetValue(LeftMouseButton, null);
case 1: return (bool)m_btnIsPressedProp.GetValue(RightMouseButton, null);
// case 2: return (bool)_btnIsPressedProp.GetValue(MiddleMouseButton, null);
default: throw new NotImplementedException();
}
}
// UI Input
public Type TInputSystemUIInputModule
=> m_tUIInputModule
?? (m_tUIInputModule = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.UI.InputSystemUIInputModule"));
internal Type m_tUIInputModule;
public BaseInputModule UIModule => m_newInputModule;
internal BaseInputModule m_newInputModule;
public void AddUIInputModule()
{
if (TInputSystemUIInputModule == null)
{
ExplorerCore.LogWarning("Unable to find UI Input Module Type, Input will not work!");
return;
}
var assetType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionAsset");
m_newInputModule = RuntimeProvider.Instance.AddComponent<BaseInputModule>(UIManager.CanvasRoot, TInputSystemUIInputModule);
var asset = RuntimeProvider.Instance.CreateScriptable(assetType)
.TryCast(assetType);
inputExtensions = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionSetupExtensions");
var addMap = inputExtensions.GetMethod("AddActionMap", new Type[] { assetType, typeof(string) });
var map = addMap.Invoke(null, new object[] { asset, "UI" })
.TryCast(ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionMap"));
CreateAction(map, "point", new[] { "<Mouse>/position" }, "point");
CreateAction(map, "click", new[] { "<Mouse>/leftButton" }, "leftClick");
CreateAction(map, "rightClick", new[] { "<Mouse>/rightButton" }, "rightClick");
CreateAction(map, "scrollWheel", new[] { "<Mouse>/scroll" }, "scrollWheel");
UI_Enable = map.GetType().GetMethod("Enable");
UI_Enable.Invoke(map, ArgumentUtility.EmptyArgs);
UI_ActionMap = map;
}
private Type inputExtensions;
private object UI_ActionMap;
private MethodInfo UI_Enable;
private void CreateAction(object map, string actionName, string[] bindings, string propertyName)
{
var inputActionType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputAction");
var addAction = inputExtensions.GetMethod("AddAction");
var action = addAction.Invoke(null, new object[] { map, actionName, default, null, null, null, null, null })
.TryCast(inputActionType);
var addBinding = inputExtensions.GetMethod("AddBinding",
new Type[] { inputActionType, typeof(string), typeof(string), typeof(string), typeof(string) });
foreach (string binding in bindings)
addBinding.Invoke(null, new object[] { action.TryCast(inputActionType), binding, null, null, null });
var refType = ReflectionUtility.GetTypeByName("UnityEngine.InputSystem.InputActionReference");
var inputRef = refType.GetMethod("Create")
.Invoke(null, new object[] { action })
.TryCast(refType);
TInputSystemUIInputModule
.GetProperty(propertyName)
.SetValue(m_newInputModule.TryCast(TInputSystemUIInputModule), inputRef, null);
}
public void ActivateModule()
{
m_newInputModule.ActivateModule();
UI_Enable.Invoke(UI_ActionMap, ArgumentUtility.EmptyArgs);
}
}
}

View File

@ -1,58 +0,0 @@
using System;
using System.Reflection;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer.UI;
namespace UnityExplorer.Core.Input
{
public class LegacyInput : IHandleInput
{
public LegacyInput()
{
m_mousePositionProp = TInput.GetProperty("mousePosition");
m_mouseDeltaProp = TInput.GetProperty("mouseScrollDelta");
m_getKeyMethod = TInput.GetMethod("GetKey", new Type[] { typeof(KeyCode) });
m_getKeyDownMethod = TInput.GetMethod("GetKeyDown", new Type[] { typeof(KeyCode) });
m_getMouseButtonMethod = TInput.GetMethod("GetMouseButton", new Type[] { typeof(int) });
m_getMouseButtonDownMethod = TInput.GetMethod("GetMouseButtonDown", new Type[] { typeof(int) });
}
public static Type TInput => m_tInput ?? (m_tInput = ReflectionUtility.GetTypeByName("UnityEngine.Input"));
private static Type m_tInput;
private static PropertyInfo m_mousePositionProp;
private static PropertyInfo m_mouseDeltaProp;
private static MethodInfo m_getKeyMethod;
private static MethodInfo m_getKeyDownMethod;
private static MethodInfo m_getMouseButtonMethod;
private static MethodInfo m_getMouseButtonDownMethod;
public Vector2 MousePosition => (Vector3)m_mousePositionProp.GetValue(null, null);
public Vector2 MouseScrollDelta => (Vector2)m_mouseDeltaProp.GetValue(null, null);
public bool GetKey(KeyCode key) => (bool)m_getKeyMethod.Invoke(null, new object[] { key });
public bool GetKeyDown(KeyCode key) => (bool)m_getKeyDownMethod.Invoke(null, new object[] { key });
public bool GetMouseButton(int btn) => (bool)m_getMouseButtonMethod.Invoke(null, new object[] { btn });
public bool GetMouseButtonDown(int btn) => (bool)m_getMouseButtonDownMethod.Invoke(null, new object[] { btn });
// UI Input module
public BaseInputModule UIModule => m_inputModule;
internal StandaloneInputModule m_inputModule;
public void AddUIInputModule()
{
m_inputModule = UIManager.CanvasRoot.gameObject.AddComponent<StandaloneInputModule>();
}
public void ActivateModule()
{
m_inputModule.ActivateModule();
}
}
}

View File

@ -1,23 +0,0 @@
using UnityEngine;
using UnityEngine.EventSystems;
namespace UnityExplorer.Core.Input
{
// Just a stub for games where no Input module was able to load at all.
public class NoInput : IHandleInput
{
public Vector2 MousePosition => Vector2.zero;
public Vector2 MouseScrollDelta => Vector2.zero;
public bool GetKey(KeyCode key) => false;
public bool GetKeyDown(KeyCode key) => false;
public bool GetMouseButton(int btn) => false;
public bool GetMouseButtonDown(int btn) => false;
public BaseInputModule UIModule => null;
public void ActivateModule() { }
public void AddUIInputModule() { }
}
}

View File

@ -1,114 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
namespace UnityExplorer
{
public static class ReflectionExtensions
{
// ReflectionUtility extensions
public static Type GetActualType(this object obj)
=> ReflectionUtility.Instance.Internal_GetActualType(obj);
public static object TryCast(this object obj)
=> ReflectionUtility.Instance.Internal_TryCast(obj, ReflectionUtility.Instance.Internal_GetActualType(obj));
public static object TryCast(this object obj, Type castTo)
=> ReflectionUtility.Instance.Internal_TryCast(obj, castTo);
public static T TryCast<T>(this object obj)
{
try
{
return (T)ReflectionUtility.Instance.Internal_TryCast(obj, typeof(T));
}
catch
{
return default;
}
}
public static HashSet<Type> GetImplementationsOf(this Type baseType, bool allowAbstract, bool allowGeneric)
=> ReflectionUtility.GetImplementationsOf(baseType, allowAbstract, allowGeneric);
// ------- Misc extensions --------
/// <summary>
/// Safely try to get all Types inside an Assembly.
/// </summary>
public static IEnumerable<Type> TryGetTypes(this Assembly asm)
{
try
{
return asm.GetTypes();
}
catch (ReflectionTypeLoadException e)
{
try
{
return asm.GetExportedTypes();
}
catch
{
return e.Types.Where(t => t != null);
}
}
catch
{
return Enumerable.Empty<Type>();
}
}
/// <summary>
/// Check if the two objects are reference-equal, including checking for UnityEngine.Object-equality and Il2CppSystem.Object-equality.
/// </summary>
public static bool ReferenceEqual(this object objA, object objB)
{
if (object.ReferenceEquals(objA, objB))
return true;
if (objA is UnityEngine.Object unityA && objB is UnityEngine.Object unityB)
{
if (unityA && unityB && unityA.m_CachedPtr == unityB.m_CachedPtr)
return true;
}
#if CPP
if (objA is Il2CppSystem.Object cppA && objB is Il2CppSystem.Object cppB
&& cppA.Pointer == cppB.Pointer)
return true;
#endif
return false;
}
/// <summary>
/// Helper to display a simple "{ExceptionType}: {Message}" of the exception, and optionally use the inner-most exception.
/// </summary>
public static string ReflectionExToString(this Exception e, bool innerMost = true)
{
if (innerMost)
e = e.GetInnerMostException();
return $"{e.GetType()}: {e.Message}";
}
public static Exception GetInnerMostException(this Exception e)
{
while (e.InnerException != null)
{
#if CPP
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException)
break;
#endif
e = e.InnerException;
}
return e;
}
}
}

View File

@ -1,945 +0,0 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using System.Runtime.InteropServices;
using System.Reflection;
using System.Collections;
using System.IO;
using System.Diagnostics.CodeAnalysis;
using UnityExplorer.Core;
using CppType = Il2CppSystem.Type;
using BF = System.Reflection.BindingFlags;
using UnityExplorer.Core.Config;
using UnhollowerBaseLib.Attributes;
namespace UnityExplorer
{
public class Il2CppReflection : ReflectionUtility
{
protected override void Initialize()
{
base.Initialize();
TryLoadGameModules();
BuildDeobfuscationCache();
OnTypeLoaded += TryCacheDeobfuscatedType;
}
#region IL2CPP Extern and pointers
// Extern C++ methods
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass);
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr il2cpp_object_get_class(IntPtr obj);
public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _);
public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr)
{
if (cppClassPointers.TryGetValue(type.AssemblyQualifiedName, out il2cppPtr))
return il2cppPtr != IntPtr.Zero;
il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
.MakeGenericType(new Type[] { type })
.GetField("NativeClassPtr", BF.Public | BF.Static)
.GetValue(null);
cppClassPointers.Add(type.AssemblyQualifiedName, il2cppPtr);
return il2cppPtr != IntPtr.Zero;
}
#endregion
#region Deobfuscation cache
private static readonly Dictionary<string, Type> DeobfuscatedTypes = new Dictionary<string, Type>();
private static readonly Dictionary<string, string> reverseDeobCache = new Dictionary<string, string>();
private static void BuildDeobfuscationCache()
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.TryGetTypes())
TryCacheDeobfuscatedType(type);
}
if (DeobfuscatedTypes.Count > 0)
ExplorerCore.Log($"Built IL2CPP deobfuscation cache, initial count: {DeobfuscatedTypes.Count}");
}
private static void TryCacheDeobfuscatedType(Type type)
{
try
{
if (!type.CustomAttributes.Any())
return;
foreach (var att in type.CustomAttributes)
{
// Thanks to Slaynash for this
if (att.AttributeType == typeof(ObfuscatedNameAttribute))
{
string obfuscatedName = att.ConstructorArguments[0].Value.ToString();
DeobfuscatedTypes.Add(obfuscatedName, type);
reverseDeobCache.Add(type.FullName, obfuscatedName);
}
}
}
catch { }
}
internal override string Internal_ProcessTypeInString(string theString, Type type)
{
if (reverseDeobCache.TryGetValue(type.FullName, out string obName))
return theString.Replace(obName, type.FullName);
return theString;
}
#endregion
// Get type by name
internal override Type Internal_GetTypeByName(string fullName)
{
if (DeobfuscatedTypes.TryGetValue(fullName, out Type deob))
return deob;
return base.Internal_GetTypeByName(fullName);
}
#region Get actual type
internal override Type Internal_GetActualType(object obj)
{
if (obj == null)
return null;
var type = obj.GetType();
try
{
if (IsString(obj))
return typeof(string);
if (IsIl2CppPrimitive(type))
return il2cppPrimitivesToMono[type.FullName];
if (obj is Il2CppSystem.Object cppObject)
{
var cppType = cppObject.GetIl2CppType();
// check if type is injected
IntPtr classPtr = il2cpp_object_get_class(cppObject.Pointer);
if (RuntimeSpecificsStore.IsInjected(classPtr))
{
// Note: This will fail on injected subclasses.
// - {Namespace}.{Class}.{Subclass} would be {Namespace}.{Subclass} when injected.
// Not sure on solution yet.
return GetTypeByName(cppType.FullName) ?? type;
}
if (AllTypes.TryGetValue(cppType.FullName, out Type primitive) && primitive.IsPrimitive)
return primitive;
return GetUnhollowedType(cppType) ?? type;
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception in IL2CPP GetActualType: " + ex);
}
return type;
}
public static Type GetUnhollowedType(CppType cppType)
{
var fullname = cppType.FullName;
if (DeobfuscatedTypes.TryGetValue(fullname, out Type deob))
return deob;
if (fullname.StartsWith("System."))
fullname = $"Il2Cpp{fullname}";
AllTypes.TryGetValue(fullname, out Type monoType);
return monoType;
}
#endregion
#region Casting
private static readonly Dictionary<string, IntPtr> cppClassPointers = new Dictionary<string, IntPtr>();
internal override object Internal_TryCast(object obj, Type castTo)
{
if (obj == null)
return null;
var type = obj.GetType();
if (type == castTo)
return obj;
// from structs
if (type.IsValueType)
{
// from il2cpp primitive to system primitive
if (IsIl2CppPrimitive(type) && castTo.IsPrimitive)
{
return MakeMonoPrimitive(obj);
}
// from system primitive to il2cpp primitive
else if (IsIl2CppPrimitive(castTo))
{
return MakeIl2CppPrimitive(castTo, obj);
}
// from other structs to il2cpp object
else if (typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
{
return BoxIl2CppObject(obj);
}
else
return obj;
}
// from string to il2cpp.Object / il2cpp.String
if (obj is string && typeof(Il2CppSystem.Object).IsAssignableFrom(castTo))
{
return BoxStringToType(obj, castTo);
}
// from il2cpp objects...
if (!(obj is Il2CppObjectBase cppObj))
return obj;
// from Il2CppSystem.Object to a struct
if (castTo.IsValueType)
return UnboxCppObject(cppObj, castTo);
// or to system string
else if (castTo == typeof(string))
return UnboxString(obj);
// Casting from il2cpp object to il2cpp object...
if (!Il2CppTypeNotNull(castTo, out IntPtr castToPtr))
return obj;
IntPtr castFromPtr = il2cpp_object_get_class(cppObj.Pointer);
if (!il2cpp_class_is_assignable_from(castToPtr, castFromPtr))
return null;
if (RuntimeSpecificsStore.IsInjected(castToPtr))
{
var injectedObj = UnhollowerBaseLib.Runtime.ClassInjectorBase.GetMonoObjectFromIl2CppPointer(cppObj.Pointer);
return injectedObj ?? obj;
}
try
{
return Activator.CreateInstance(castTo, cppObj.Pointer);
}
catch
{
return obj;
}
}
//private static bool IsAssignableFrom(Type thisType, Type fromType)
//{
// if (!Il2CppTypeNotNull(fromType, out IntPtr fromTypePtr)
// || !Il2CppTypeNotNull(thisType, out IntPtr thisTypePtr))
// {
// // one or both of the types are not Il2Cpp types, use normal check
// return thisType.IsAssignableFrom(fromType);
// }
//
// return il2cpp_class_is_assignable_from(thisTypePtr, fromTypePtr);
//}
#endregion
#region Boxing and unboxing ValueTypes
// cached il2cpp unbox methods
internal static readonly Dictionary<string, MethodInfo> unboxMethods = new Dictionary<string, MethodInfo>();
// Unbox an il2cpp object to a struct or System primitive.
public object UnboxCppObject(Il2CppObjectBase cppObj, Type toType)
{
if (!toType.IsValueType)
return null;
try
{
if (toType.IsEnum)
return Enum.Parse(toType, cppObj.ToString());
var name = toType.AssemblyQualifiedName;
if (!unboxMethods.ContainsKey(name))
{
unboxMethods.Add(name, typeof(Il2CppObjectBase)
.GetMethod("Unbox")
.MakeGenericMethod(toType));
}
return unboxMethods[name].Invoke(cppObj, ArgumentUtility.EmptyArgs);
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception Unboxing Il2Cpp object to struct: " + ex);
return null;
}
}
private static Il2CppSystem.Object BoxIl2CppObject(object cppStruct, Type structType)
{
return GetMethodInfo(structType, "BoxIl2CppObject", ArgumentUtility.EmptyTypes)
.Invoke(cppStruct, ArgumentUtility.EmptyArgs)
as Il2CppSystem.Object;
}
public Il2CppSystem.Object BoxIl2CppObject(object value)
{
if (value == null)
return null;
try
{
var type = value.GetType();
if (!type.IsValueType)
return null;
if (type.IsEnum)
return Il2CppSystem.Enum.Parse(Il2CppType.From(type), value.ToString());
if (type.IsPrimitive && AllTypes.TryGetValue($"Il2Cpp{type.FullName}", out Type cppType))
return BoxIl2CppObject(MakeIl2CppPrimitive(cppType, value), cppType);
return BoxIl2CppObject(value, type);
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception in BoxIl2CppObject: " + ex);
return null;
}
}
// Helpers for Il2Cpp primitive <-> Mono
internal static readonly Dictionary<string, Type> il2cppPrimitivesToMono = new Dictionary<string, Type>
{
{ "Il2CppSystem.Boolean", typeof(bool) },
{ "Il2CppSystem.Byte", typeof(byte) },
{ "Il2CppSystem.SByte", typeof(sbyte) },
{ "Il2CppSystem.Char", typeof(char) },
{ "Il2CppSystem.Double", typeof(double) },
{ "Il2CppSystem.Single", typeof(float) },
{ "Il2CppSystem.Int32", typeof(int) },
{ "Il2CppSystem.UInt32", typeof(uint) },
{ "Il2CppSystem.Int64", typeof(long) },
{ "Il2CppSystem.UInt64", typeof(ulong) },
{ "Il2CppSystem.Int16", typeof(short) },
{ "Il2CppSystem.UInt16", typeof(ushort) },
{ "Il2CppSystem.IntPtr", typeof(IntPtr) },
{ "Il2CppSystem.UIntPtr", typeof(UIntPtr) }
};
public static bool IsIl2CppPrimitive(object obj) => IsIl2CppPrimitive(obj.GetType());
public static bool IsIl2CppPrimitive(Type type) => il2cppPrimitivesToMono.ContainsKey(type.FullName);
public object MakeMonoPrimitive(object cppPrimitive)
{
return GetFieldInfo(cppPrimitive.GetType(), "m_value").GetValue(cppPrimitive);
}
public object MakeIl2CppPrimitive(Type cppType, object monoValue)
{
var cppStruct = Activator.CreateInstance(cppType);
GetFieldInfo(cppType, "m_value").SetValue(cppStruct, monoValue);
return cppStruct;
}
#endregion
#region String boxing/unboxing
private const string IL2CPP_STRING_FULLNAME = "Il2CppSystem.String";
private const string STRING_FULLNAME = "System.String";
public bool IsString(object obj)
{
if (obj is string || obj is Il2CppSystem.String)
return true;
if (obj is Il2CppSystem.Object cppObj)
{
var type = cppObj.GetIl2CppType();
return type.FullName == IL2CPP_STRING_FULLNAME || type.FullName == STRING_FULLNAME;
}
return false;
}
public object BoxStringToType(object value, Type castTo)
{
if (castTo == typeof(Il2CppSystem.String))
return (Il2CppSystem.String)(value as string);
else
return (Il2CppSystem.Object)(value as string);
}
public string UnboxString(object value)
{
if (value is string s)
return s;
s = null;
if (value is Il2CppSystem.Object cppObject)
s = cppObject.ToString();
else if (value is Il2CppSystem.String cppString)
s = cppString;
return s;
}
#endregion
#region Singleton finder
internal override void Internal_FindSingleton(string[] possibleNames, Type type, BF flags, List<object> instances)
{
PropertyInfo pi;
foreach (var name in possibleNames)
{
pi = type.GetProperty(name, flags);
if (pi != null)
{
var instance = pi.GetValue(null, null);
if (instance != null)
{
instances.Add(instance);
return;
}
}
}
base.Internal_FindSingleton(possibleNames, type, flags, instances);
}
#endregion
#region Force-loading game modules
internal static string UnhollowedFolderPath => Path.GetFullPath(
#if ML
Path.Combine("MelonLoader", "Managed")
#elif BIE
Path.Combine("BepInEx", "unhollowed")
#else
Path.Combine(ExplorerCore.Loader.ExplorerFolder, "Modules")
#endif
);
// Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded.
// Force loading all il2cpp modules
internal void TryLoadGameModules()
{
if (Directory.Exists(UnhollowedFolderPath))
{
var files = Directory.GetFiles(UnhollowedFolderPath);
foreach (var filePath in files)
{
try
{
DoLoadModule(filePath, true);
}
catch //(Exception ex)
{
//ExplorerCore.LogWarning($"Failed to force-load module '{name}': {ex.ReflectionExToString()}");
}
}
}
else
ExplorerCore.LogWarning($"Expected Unhollowed folder path does not exist: '{UnhollowedFolderPath}'");
}
internal bool DoLoadModule(string fullPath, bool suppressWarning = false)
{
if (!File.Exists(fullPath))
return false;
try
{
Assembly.LoadFile(fullPath);
//Assembly.Load(File.ReadAllBytes(fullPath));
return true;
}
catch (Exception e)
{
if (!suppressWarning)
Console.WriteLine($"Failed loading module '{Path.GetFileName(fullPath)}'! {e.ReflectionExToString()}");
}
return false;
}
#endregion
#region Il2cpp reflection blacklist
public override string DefaultReflectionBlacklist => string.Join(";", defaultIl2CppBlacklist);
// These methods currently cause a crash in most il2cpp games,
// even from doing "GetParameters()" on the MemberInfo.
// Blacklisting until the issue is fixed in Unhollower.
public static HashSet<string> defaultIl2CppBlacklist = new HashSet<string>
{
// These were deprecated a long time ago, still show up in some IL2CPP games for some reason
"UnityEngine.MonoBehaviour.allowPrefabModeInPlayMode",
"UnityEngine.MonoBehaviour.runInEditMode",
"UnityEngine.Component.animation",
"UnityEngine.Component.audio",
"UnityEngine.Component.camera",
"UnityEngine.Component.collider",
"UnityEngine.Component.collider2D",
"UnityEngine.Component.constantForce",
"UnityEngine.Component.hingeJoint",
"UnityEngine.Component.light",
"UnityEngine.Component.networkView",
"UnityEngine.Component.particleSystem",
"UnityEngine.Component.renderer",
"UnityEngine.Component.rigidbody",
"UnityEngine.Component.rigidbody2D",
"UnityEngine.Light.flare",
// These can cause a crash in IL2CPP
"Il2CppSystem.Type.DeclaringMethod",
"Il2CppSystem.RuntimeType.DeclaringMethod",
"Unity.Jobs.LowLevel.Unsafe.JobsUtility.CreateJobReflectionData",
"Unity.Profiling.ProfilerRecorder.CopyTo",
"Unity.Profiling.ProfilerRecorder.StartNew",
"UnityEngine.Analytics.Analytics.RegisterEvent",
"UnityEngine.Analytics.Analytics.SendEvent",
"UnityEngine.Analytics.ContinuousEvent+ConfigureEventDelegate.Invoke",
"UnityEngine.Analytics.ContinuousEvent.ConfigureEvent",
"UnityEngine.Animations.AnimationLayerMixerPlayable.Create",
"UnityEngine.Animations.AnimationLayerMixerPlayable.CreateHandle",
"UnityEngine.Animations.AnimationMixerPlayable.Create",
"UnityEngine.Animations.AnimationMixerPlayable.CreateHandle",
"UnityEngine.AssetBundle.RecompressAssetBundleAsync",
"UnityEngine.Audio.AudioMixerPlayable.Create",
"UnityEngine.BoxcastCommand.ScheduleBatch",
"UnityEngine.Camera.CalculateProjectionMatrixFromPhysicalProperties",
"UnityEngine.CapsulecastCommand.ScheduleBatch",
"UnityEngine.Collider2D.Cast",
"UnityEngine.Collider2D.Raycast",
"UnityEngine.ComputeBuffer+BeginBufferWriteDelegate.Invoke",
"UnityEngine.ComputeBuffer+EndBufferWriteDelegate.Invoke",
"UnityEngine.ComputeBuffer.BeginBufferWrite",
"UnityEngine.ComputeBuffer.EndBufferWrite",
"UnityEngine.Cubemap+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Cubemap+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Cubemap.SetPixelDataImpl",
"UnityEngine.Cubemap.SetPixelDataImplArray",
"UnityEngine.CubemapArray+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.CubemapArray+SetPixelDataImplDelegate.Invoke",
"UnityEngine.CubemapArray.SetPixelDataImpl",
"UnityEngine.CubemapArray.SetPixelDataImplArray",
"UnityEngine.Experimental.Playables.MaterialEffectPlayable.Create",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure+AddInstanceDelegate.Invoke",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure+AddInstance_Procedural_InjectedDelegate.Invoke",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance_Procedural",
"UnityEngine.Experimental.Rendering.RayTracingAccelerationStructure.AddInstance_Procedural_Injected",
"UnityEngine.Experimental.Rendering.RayTracingShader+DispatchDelegate.Invoke",
"UnityEngine.Experimental.Rendering.RayTracingShader.Dispatch",
"UnityEngine.Experimental.Rendering.RenderPassAttachment.Clear",
"UnityEngine.GUI.DoButtonGrid",
"UnityEngine.GUI.Slider",
"UnityEngine.GUI.Toolbar",
"UnityEngine.Graphics.DrawMeshInstancedIndirect",
"UnityEngine.Graphics.DrawMeshInstancedProcedural",
"UnityEngine.Graphics.DrawProcedural",
"UnityEngine.Graphics.DrawProceduralIndirect",
"UnityEngine.Graphics.DrawProceduralIndirectNow",
"UnityEngine.Graphics.DrawProceduralNow",
"UnityEngine.LineRenderer+BakeMeshDelegate.Invoke",
"UnityEngine.LineRenderer.BakeMesh",
"UnityEngine.Mesh.GetIndices",
"UnityEngine.Mesh.GetTriangles",
"UnityEngine.Mesh.SetIndices",
"UnityEngine.Mesh.SetTriangles",
"UnityEngine.Physics2D.BoxCast",
"UnityEngine.Physics2D.CapsuleCast",
"UnityEngine.Physics2D.CircleCast",
"UnityEngine.PhysicsScene.BoxCast",
"UnityEngine.PhysicsScene.CapsuleCast",
"UnityEngine.PhysicsScene.OverlapBox",
"UnityEngine.PhysicsScene.OverlapCapsule",
"UnityEngine.PhysicsScene.SphereCast",
"UnityEngine.PhysicsScene2D.BoxCast",
"UnityEngine.PhysicsScene2D.CapsuleCast",
"UnityEngine.PhysicsScene2D.CircleCast",
"UnityEngine.PhysicsScene2D.GetRayIntersection",
"UnityEngine.PhysicsScene2D.Linecast",
"UnityEngine.PhysicsScene2D.OverlapArea",
"UnityEngine.PhysicsScene2D.OverlapBox",
"UnityEngine.PhysicsScene2D.OverlapCapsule",
"UnityEngine.PhysicsScene2D.OverlapCircle",
"UnityEngine.PhysicsScene2D.OverlapCollider",
"UnityEngine.PhysicsScene2D.OverlapPoint",
"UnityEngine.PhysicsScene2D.Raycast",
"UnityEngine.Playables.Playable.Create",
"UnityEngine.Profiling.CustomSampler.Create",
"UnityEngine.RaycastCommand.ScheduleBatch",
"UnityEngine.RemoteConfigSettings+QueueConfigDelegate.Invoke",
"UnityEngine.RemoteConfigSettings.QueueConfig",
"UnityEngine.RenderTexture.GetTemporaryImpl",
"UnityEngine.Rendering.AsyncGPUReadback.Request",
"UnityEngine.Rendering.AttachmentDescriptor.ConfigureClear",
"UnityEngine.Rendering.BatchRendererGroup+AddBatch_InjectedDelegate.Invoke",
"UnityEngine.Rendering.BatchRendererGroup.AddBatch",
"UnityEngine.Rendering.BatchRendererGroup.AddBatch_Injected",
"UnityEngine.Rendering.CommandBuffer+Internal_DispatchRaysDelegate.Invoke",
"UnityEngine.Rendering.CommandBuffer.DispatchRays",
"UnityEngine.Rendering.CommandBuffer.DrawMeshInstancedProcedural",
"UnityEngine.Rendering.CommandBuffer.Internal_DispatchRays",
"UnityEngine.Rendering.CommandBuffer.ResolveAntiAliasedSurface",
"UnityEngine.Rendering.ScriptableRenderContext.BeginRenderPass",
"UnityEngine.Rendering.ScriptableRenderContext.BeginScopedRenderPass",
"UnityEngine.Rendering.ScriptableRenderContext.BeginScopedSubPass",
"UnityEngine.Rendering.ScriptableRenderContext.BeginSubPass",
"UnityEngine.Rendering.ScriptableRenderContext.SetupCameraProperties",
"UnityEngine.Rigidbody2D.Cast",
"UnityEngine.Scripting.GarbageCollector+CollectIncrementalDelegate.Invoke",
"UnityEngine.Scripting.GarbageCollector.CollectIncremental",
"UnityEngine.SpherecastCommand.ScheduleBatch",
"UnityEngine.Texture.GetPixelDataSize",
"UnityEngine.Texture.GetPixelDataOffset",
"UnityEngine.Texture.GetPixelDataOffset",
"UnityEngine.Texture2D+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Texture2D+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Texture2D.SetPixelDataImpl",
"UnityEngine.Texture2D.SetPixelDataImplArray",
"UnityEngine.Texture2DArray+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Texture2DArray+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Texture2DArray.SetPixelDataImpl",
"UnityEngine.Texture2DArray.SetPixelDataImplArray",
"UnityEngine.Texture3D+SetPixelDataImplArrayDelegate.Invoke",
"UnityEngine.Texture3D+SetPixelDataImplDelegate.Invoke",
"UnityEngine.Texture3D.SetPixelDataImpl",
"UnityEngine.Texture3D.SetPixelDataImplArray",
"UnityEngine.TrailRenderer+BakeMeshDelegate.Invoke",
"UnityEngine.TrailRenderer.BakeMesh",
"UnityEngine.WWW.LoadFromCacheOrDownload",
"UnityEngine.XR.InputDevice.SendHapticImpulse",
};
#endregion
#region IL2CPP IEnumerable and IDictionary
protected override bool Internal_TryGetEntryType(Type enumerableType, out Type type)
{
// Check for system types (not unhollowed)
if (base.Internal_TryGetEntryType(enumerableType, out type))
return true;
// Type is either an IL2CPP enumerable, or its not generic.
if (type.IsGenericType)
{
// Temporary naive solution until IL2CPP interface support improves.
// This will work fine for most cases, but there are edge cases which would not work.
type = type.GetGenericArguments()[0];
return true;
}
// Unable to determine entry type
type = typeof(object);
return false;
}
protected override bool Internal_TryGetEntryTypes(Type type, out Type keys, out Type values)
{
if (base.Internal_TryGetEntryTypes(type, out keys, out values))
return true;
// Type is either an IL2CPP dictionary, or its not generic.
if (type.IsGenericType)
{
// Naive solution until IL2CPP interfaces improve.
var args = type.GetGenericArguments();
if (args.Length == 2)
{
keys = args[0];
values = args[1];
return true;
}
}
keys = typeof(object);
values = typeof(object);
return false;
}
// Temp fix until Unhollower interface support improves
internal static readonly Dictionary<string, MethodInfo> getEnumeratorMethods = new Dictionary<string, MethodInfo>();
internal static readonly Dictionary<string, EnumeratorInfo> enumeratorInfos = new Dictionary<string, EnumeratorInfo>();
internal static readonly HashSet<string> notSupportedTypes = new HashSet<string>();
// IEnumerables
internal static IntPtr cppIEnumerablePointer;
protected override bool Internal_IsEnumerable(Type type)
{
if (base.Internal_IsEnumerable(type))
return true;
try
{
if (cppIEnumerablePointer == IntPtr.Zero)
Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IEnumerable), out cppIEnumerablePointer);
if (cppIEnumerablePointer != IntPtr.Zero
&& Il2CppTypeNotNull(type, out IntPtr assignFromPtr)
&& il2cpp_class_is_assignable_from(cppIEnumerablePointer, assignFromPtr))
{
return true;
}
}
catch { }
return false;
}
internal class EnumeratorInfo
{
internal MethodInfo moveNext;
internal PropertyInfo current;
}
protected override bool Internal_TryGetEnumerator(object list, out IEnumerator enumerator)
{
if (list is IEnumerable)
return base.Internal_TryGetEnumerator(list, out enumerator);
try
{
PrepareCppEnumerator(list, out object cppEnumerator, out EnumeratorInfo info);
enumerator = EnumerateCppList(info, cppEnumerator);
return true;
}
catch //(Exception ex)
{
//ExplorerCore.LogWarning($"Exception enumerating IEnumerable: {ex.ReflectionExToString()}");
enumerator = null;
return false;
}
}
private static void PrepareCppEnumerator(object list, out object cppEnumerator, out EnumeratorInfo info)
{
info = null;
cppEnumerator = null;
if (list == null)
throw new ArgumentNullException("list");
// Some ugly reflection to use the il2cpp interface for the instance type
var type = list.GetType();
var key = type.AssemblyQualifiedName;
if (!getEnumeratorMethods.ContainsKey(key))
{
getEnumeratorMethods.Add(key, type.GetMethod("GetEnumerator"));
// ensure the enumerator type is supported
try
{
var test = getEnumeratorMethods[key].Invoke(list, null);
test.GetType().GetMethod("MoveNext").Invoke(test, null);
}
catch
{
notSupportedTypes.Add(key);
}
}
if (notSupportedTypes.Contains(key))
throw new NotSupportedException($"The IEnumerable type '{type.FullName}' does not support MoveNext.");
cppEnumerator = getEnumeratorMethods[key].Invoke(list, null);
var enumeratorType = cppEnumerator.GetType();
var enumInfoKey = enumeratorType.AssemblyQualifiedName;
if (!enumeratorInfos.ContainsKey(enumInfoKey))
{
enumeratorInfos.Add(enumInfoKey, new EnumeratorInfo
{
current = enumeratorType.GetProperty("Current"),
moveNext = enumeratorType.GetMethod("MoveNext"),
});
}
info = enumeratorInfos[enumInfoKey];
}
internal static IEnumerator EnumerateCppList(EnumeratorInfo info, object enumerator)
{
// Yield and return the actual entries
while ((bool)info.moveNext.Invoke(enumerator, null))
yield return info.current.GetValue(enumerator);
}
// IDictionary
internal static IntPtr cppIDictionaryPointer;
protected override bool Internal_IsDictionary(Type type)
{
if (base.Internal_IsDictionary(type))
return true;
try
{
if (cppIDictionaryPointer == IntPtr.Zero)
if (!Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IDictionary), out cppIDictionaryPointer))
return false;
if (Il2CppTypeNotNull(type, out IntPtr classPtr)
&& il2cpp_class_is_assignable_from(cppIDictionaryPointer, classPtr))
return true;
}
catch { }
return false;
}
protected override bool Internal_TryGetDictEnumerator(object dictionary, out IEnumerator<DictionaryEntry> dictEnumerator)
{
if (dictionary is IDictionary)
return base.Internal_TryGetDictEnumerator(dictionary, out dictEnumerator);
try
{
var type = dictionary.GetType();
if (typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(type))
{
dictEnumerator = EnumerateCppHashTable(dictionary.TryCast<Il2CppSystem.Collections.Hashtable>());
return true;
}
var keys = type.GetProperty("Keys").GetValue(dictionary, null);
var keyCollType = keys.GetType();
var cacheKey = keys.GetType().AssemblyQualifiedName;
if (!getEnumeratorMethods.ContainsKey(cacheKey))
{
getEnumeratorMethods.Add(cacheKey, keyCollType.GetMethod("GetEnumerator"));
// test support
try
{
var test = getEnumeratorMethods[cacheKey].Invoke(keys, null);
test.GetType().GetMethod("MoveNext").Invoke(test, null);
}
catch
{
notSupportedTypes.Add(cacheKey);
}
}
if (notSupportedTypes.Contains(cacheKey))
throw new Exception($"The IDictionary type '{type.FullName}' does not support MoveNext.");
var keyEnumerator = getEnumeratorMethods[cacheKey].Invoke(keys, null);
var keyInfo = new EnumeratorInfo
{
current = keyEnumerator.GetType().GetProperty("Current"),
moveNext = keyEnumerator.GetType().GetMethod("MoveNext"),
};
var values = type.GetProperty("Values").GetValue(dictionary, null);
var valueEnumerator = values.GetType().GetMethod("GetEnumerator").Invoke(values, null);
var valueInfo = new EnumeratorInfo
{
current = valueEnumerator.GetType().GetProperty("Current"),
moveNext = valueEnumerator.GetType().GetMethod("MoveNext"),
};
dictEnumerator = EnumerateCppDict(keyInfo, keyEnumerator, valueInfo, valueEnumerator);
return true;
}
catch //(Exception ex)
{
//ExplorerCore.LogWarning($"Exception enumerating IDictionary: {ex.ReflectionExToString()}");
dictEnumerator = null;
return false;
}
}
internal static IEnumerator<DictionaryEntry> EnumerateCppDict(EnumeratorInfo keyInfo, object keyEnumerator,
EnumeratorInfo valueInfo, object valueEnumerator)
{
while ((bool)keyInfo.moveNext.Invoke(keyEnumerator, null))
{
valueInfo.moveNext.Invoke(valueEnumerator, null);
var key = keyInfo.current.GetValue(keyEnumerator, null);
var value = valueInfo.current.GetValue(valueEnumerator, null);
yield return new DictionaryEntry(key, value);
}
}
internal static IEnumerator<DictionaryEntry> EnumerateCppHashTable(Il2CppSystem.Collections.Hashtable hashtable)
{
for (int i = 0; i < hashtable.buckets.Count; i++)
{
var bucket = hashtable.buckets[i];
if (bucket == null || bucket.key == null)
continue;
yield return new DictionaryEntry(bucket.key, bucket.val);
}
}
#endregion
}
}
#endif

View File

@ -1,586 +0,0 @@
using System;
using System.Collections;
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;
namespace UnityExplorer
{
public class ReflectionUtility
{
public const BF FLAGS = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
internal static ReflectionUtility Instance;
public static void Init()
{
Instance =
#if CPP
new Il2CppReflection();
#else
new ReflectionUtility();
#endif
Instance.Initialize();
}
protected virtual void Initialize()
{
SetupTypeCache();
LoadBlacklistString(ConfigManager.Reflection_Signature_Blacklist.Value);
ConfigManager.Reflection_Signature_Blacklist.OnValueChanged += LoadBlacklistString;
}
#region Type cache
public static Action<Type> OnTypeLoaded;
/// <summary>Key: Type.FullName</summary>
public static readonly SortedDictionary<string, Type> AllTypes = new SortedDictionary<string, Type>(StringComparer.OrdinalIgnoreCase);
public static readonly List<string> AllNamespaces = new List<string>();
private static readonly HashSet<string> uniqueNamespaces = new HashSet<string>();
private static string[] allTypesArray;
public static string[] GetTypeNameArray()
{
if (allTypesArray == null || allTypesArray.Length != AllTypes.Count)
{
allTypesArray = new string[AllTypes.Count];
int i = 0;
foreach (var name in AllTypes.Keys)
{
allTypesArray[i] = name;
i++;
}
}
return allTypesArray;
}
private static void SetupTypeCache()
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
CacheTypes(asm);
AppDomain.CurrentDomain.AssemblyLoad += AssemblyLoaded;
}
private static void AssemblyLoaded(object sender, AssemblyLoadEventArgs args)
{
if (args.LoadedAssembly == null || args.LoadedAssembly.GetName().Name == "completions")
return;
CacheTypes(args.LoadedAssembly);
}
private static void CacheTypes(Assembly asm)
{
foreach (var type in asm.TryGetTypes())
{
if (!string.IsNullOrEmpty(type.Namespace) && !uniqueNamespaces.Contains(type.Namespace))
{
uniqueNamespaces.Add(type.Namespace);
int i = 0;
while (i < AllNamespaces.Count)
{
if (type.Namespace.CompareTo(AllNamespaces[i]) < 0)
break;
i++;
}
AllNamespaces.Insert(i, type.Namespace);
}
if (AllTypes.ContainsKey(type.FullName))
AllTypes[type.FullName] = type;
else
{
AllTypes.Add(type.FullName, type);
//allTypeNames.Add(type.FullName);
}
OnTypeLoaded?.Invoke(type);
foreach (var key in typeInheritance.Keys)
{
try
{
var baseType = AllTypes[key];
if (baseType.IsAssignableFrom(type) && !typeInheritance[key].Contains(type))
typeInheritance[key].Add(type);
}
catch { }
}
}
}
#endregion
/// <summary>
/// Find a <see cref="Type"/> in the current AppDomain whose <see cref="Type.FullName"/> matches the provided <paramref name="fullName"/>.
/// </summary>
/// <param name="fullName">The <see cref="Type.FullName"/> you want to search for - case sensitive and full matches only.</param>
/// <returns>The Type if found, otherwise null.</returns>
public static Type GetTypeByName(string fullName)
=> Instance.Internal_GetTypeByName(fullName);
internal virtual Type Internal_GetTypeByName(string fullName)
{
AllTypes.TryGetValue(fullName, out Type type);
return type;
}
// Getting the actual type of an object
internal virtual Type Internal_GetActualType(object obj)
=> obj?.GetType();
// Force-casting an object to a type
internal virtual object Internal_TryCast(object obj, Type castTo)
=> obj;
// Processing deobfuscated type names in strings
public static string ProcessTypeInString(Type type, string theString)
=> Instance.Internal_ProcessTypeInString(theString, type);
internal virtual string Internal_ProcessTypeInString(string theString, Type type)
=> theString;
//// 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);
internal virtual void Internal_FindSingleton(string[] possibleNames, Type type, BindingFlags flags, List<object> instances)
{
// Look for a typical Instance backing field.
FieldInfo fi;
foreach (var name in possibleNames)
{
fi = type.GetField(name, flags);
if (fi != null)
{
var instance = fi.GetValue(null);
if (instance != null)
{
instances.Add(instance);
return;
}
}
}
}
// Universal helpers
#region Type inheritance cache
// cache for GetBaseTypes
internal static readonly Dictionary<string, Type[]> baseTypes = new Dictionary<string, Type[]>();
/// <summary>
/// Get all base types of the provided Type, including itself.
/// </summary>
public static Type[] GetAllBaseTypes(object obj) => GetAllBaseTypes(obj?.GetActualType());
/// <summary>
/// Get all base types of the provided Type, including itself.
/// </summary>
public static Type[] GetAllBaseTypes(Type type)
{
if (type == null)
throw new ArgumentNullException("type");
var name = type.AssemblyQualifiedName;
if (baseTypes.TryGetValue(name, out Type[] ret))
return ret;
List<Type> list = new List<Type>();
while (type != null)
{
list.Add(type);
type = type.BaseType;
}
ret = list.ToArray();
baseTypes.Add(name, ret);
return ret;
}
#endregion
#region Type and Generic Parameter implementation cache
// cache for GetImplementationsOf
internal static readonly Dictionary<string, HashSet<Type>> typeInheritance = new Dictionary<string, HashSet<Type>>();
internal static readonly Dictionary<string, HashSet<Type>> genericParameterInheritance = new Dictionary<string, HashSet<Type>>();
public static string GetImplementationKey(Type type)
{
if (!type.IsGenericParameter)
return type.FullName;
else
{
var sb = new StringBuilder();
sb.Append(type.GenericParameterAttributes)
.Append('|');
foreach (var c in type.GetGenericParameterConstraints())
sb.Append(c.FullName).Append(',');
return sb.ToString();
}
}
/// <summary>
/// Get all non-abstract implementations of the provided type (include itself, if not abstract) in the current AppDomain.
/// Also works for generic parameters by analyzing the constraints.
/// </summary>
/// <param name="baseType">The base type, which can optionally be abstract / interface.</param>
/// <returns>All implementations of the type in the current AppDomain.</returns>
public static HashSet<Type> GetImplementationsOf(Type baseType, bool allowAbstract, bool allowGeneric, bool allowRecursive = true)
{
var key = GetImplementationKey(baseType);
int count = AllTypes.Count;
HashSet<Type> ret;
if (!baseType.IsGenericParameter)
ret = GetImplementations(key, baseType, allowAbstract, allowGeneric);
else
ret = GetGenericParameterImplementations(key, baseType, allowAbstract, allowGeneric);
// types were resolved during the parse, do it again if we're not already rebuilding.
if (allowRecursive && AllTypes.Count != count)
{
ret = GetImplementationsOf(baseType, allowAbstract, allowGeneric, false);
}
return ret;
}
private static HashSet<Type> GetImplementations(string key, Type baseType, bool allowAbstract, bool allowGeneric)
{
if (!typeInheritance.ContainsKey(key))
{
var set = new HashSet<Type>();
var names = GetTypeNameArray();
for (int i = 0; i < names.Length; i++)
{
var name = names[i];
try
{
var type = AllTypes[name];
if (set.Contains(type)
|| (type.IsAbstract && type.IsSealed) // ignore static classes
|| (!allowAbstract && type.IsAbstract)
|| (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition)))
continue;
if (type.FullName.Contains("PrivateImplementationDetails")
|| type.FullName.Contains("DisplayClass")
|| type.FullName.Contains('<'))
continue;
if (baseType.IsAssignableFrom(type) && !set.Contains(type))
set.Add(type);
}
catch { }
}
//set.
typeInheritance.Add(key, set);
}
return typeInheritance[key];
}
private static HashSet<Type> GetGenericParameterImplementations(string key, Type baseType, bool allowAbstract, bool allowGeneric)
{
if (!genericParameterInheritance.ContainsKey(key))
{
var set = new HashSet<Type>();
var names = GetTypeNameArray();
for (int i = 0; i < names.Length; i++)
{
var name = names[i];
try
{
var type = AllTypes[name];
if (set.Contains(type)
|| (type.IsAbstract && type.IsSealed) // ignore static classes
|| (!allowAbstract && type.IsAbstract)
|| (!allowGeneric && (type.IsGenericType || type.IsGenericTypeDefinition)))
continue;
if (type.FullName.Contains("PrivateImplementationDetails")
|| type.FullName.Contains("DisplayClass")
|| type.FullName.Contains('<'))
continue;
if (baseType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.NotNullableValueTypeConstraint)
&& type.IsClass)
continue;
if (baseType.GenericParameterAttributes.HasFlag(GenericParameterAttributes.ReferenceTypeConstraint)
&& type.IsValueType)
continue;
if (baseType.GetGenericParameterConstraints().Any(it => !it.IsAssignableFrom(type)))
continue;
set.Add(type);
}
catch { }
}
genericParameterInheritance.Add(key, set);
}
return genericParameterInheritance[key];
}
#endregion
#region Internal MemberInfo Cache
internal static Dictionary<Type, Dictionary<string, FieldInfo>> fieldInfos = new Dictionary<Type, Dictionary<string, FieldInfo>>();
public static FieldInfo GetFieldInfo(Type type, string fieldName)
{
if (!fieldInfos.ContainsKey(type))
fieldInfos.Add(type, new Dictionary<string, FieldInfo>());
if (!fieldInfos[type].ContainsKey(fieldName))
fieldInfos[type].Add(fieldName, type.GetField(fieldName, FLAGS));
return fieldInfos[type][fieldName];
}
internal static Dictionary<Type, Dictionary<string, PropertyInfo>> propertyInfos = new Dictionary<Type, Dictionary<string, PropertyInfo>>();
public static PropertyInfo GetPropertyInfo(Type type, string propertyName)
{
if (!propertyInfos.ContainsKey(type))
propertyInfos.Add(type, new Dictionary<string, PropertyInfo>());
if (!propertyInfos[type].ContainsKey(propertyName))
propertyInfos[type].Add(propertyName, type.GetProperty(propertyName, FLAGS));
return propertyInfos[type][propertyName];
}
internal static Dictionary<Type, Dictionary<string, MethodInfo>> methodInfos = new Dictionary<Type, Dictionary<string, MethodInfo>>();
public static MethodInfo GetMethodInfo(Type type, string methodName)
=> GetMethodInfo(type, methodName, ArgumentUtility.EmptyTypes, false);
public static MethodInfo GetMethodInfo(Type type, string methodName, Type[] argumentTypes, bool cacheAmbiguous = false)
{
if (!methodInfos.ContainsKey(type))
methodInfos.Add(type, new Dictionary<string, MethodInfo>());
if (cacheAmbiguous)
{
methodName += "|";
foreach (var arg in argumentTypes)
methodName += arg.FullName + ",";
}
try
{
if (!methodInfos[type].ContainsKey(methodName))
{
if (argumentTypes != null)
methodInfos[type].Add(methodName, type.GetMethod(methodName, FLAGS, null, argumentTypes, null));
else
methodInfos[type].Add(methodName, type.GetMethod(methodName, FLAGS));
}
return methodInfos[type][methodName];
}
catch (AmbiguousMatchException)
{
ExplorerCore.LogWarning($"AmbiguousMatchException trying to get method '{methodName}'");
return null;
}
catch (Exception e)
{
ExplorerCore.LogWarning($"{e.GetType()} trying to get method '{methodName}': {e.Message}\r\n{e.StackTrace}");
return null;
}
}
#endregion
#region Reflection Blacklist
public virtual string DefaultReflectionBlacklist => string.Empty;
public static void LoadBlacklistString(string blacklist)
{
if (string.Equals(blacklist, "DEFAULT", StringComparison.InvariantCultureIgnoreCase))
{
blacklist = Instance.DefaultReflectionBlacklist;
ConfigManager.Reflection_Signature_Blacklist.Value = blacklist;
ConfigManager.Handler.SaveConfig();
}
if (string.IsNullOrEmpty(blacklist))
return;
var sigs = blacklist.Split(';');
foreach (var sig in sigs)
{
var s = sig.Trim();
if (string.IsNullOrEmpty(s))
continue;
if (!currentBlacklist.Contains(s))
currentBlacklist.Add(s);
}
Mono.CSharp.IL2CPP.Blacklist.SignatureBlacklist = currentBlacklist;
}
public static bool IsBlacklisted(MemberInfo member)
{
if (string.IsNullOrEmpty(member.DeclaringType?.Namespace))
return false;
var sig = $"{member.DeclaringType.FullName}.{member.Name}";
return currentBlacklist.Contains(sig);
}
private static readonly HashSet<string> currentBlacklist = new HashSet<string>();
#endregion
// Temp fix for IL2CPP until interface support improves
// IsEnumerable
public static bool IsEnumerable(Type type) => Instance.Internal_IsEnumerable(type);
protected virtual bool Internal_IsEnumerable(Type type)
{
return typeof(IEnumerable).IsAssignableFrom(type);
}
// TryGetEnumerator (list)
public static bool TryGetEnumerator(object list, out IEnumerator enumerator)
=> Instance.Internal_TryGetEnumerator(list, out enumerator);
protected virtual bool Internal_TryGetEnumerator(object list, out IEnumerator enumerator)
{
enumerator = (list as IEnumerable).GetEnumerator();
return true;
}
// TryGetEntryType
public static bool TryGetEntryType(Type enumerableType, out Type type)
=> Instance.Internal_TryGetEntryType(enumerableType, out type);
protected virtual bool Internal_TryGetEntryType(Type enumerableType, out Type type)
{
// Check for arrays
if (enumerableType.IsArray)
{
type = enumerableType.GetElementType();
return true;
}
// Check for implementation of IEnumerable<T>, IList<T> or ICollection<T>
foreach (var t in enumerableType.GetInterfaces())
{
if (t.IsGenericType)
{
var typeDef = t.GetGenericTypeDefinition();
if (typeDef == typeof(IEnumerable<>) || typeDef == typeof(IList<>) || typeDef == typeof(ICollection<>))
{
type = t.GetGenericArguments()[0];
return true;
}
}
}
// Unable to determine any generic element type, just use object.
type = typeof(object);
return false;
}
// IsDictionary
public static bool IsDictionary(Type type) => Instance.Internal_IsDictionary(type);
protected virtual bool Internal_IsDictionary(Type type)
{
return typeof(IDictionary).IsAssignableFrom(type);
}
// TryGetEnumerator (dictionary)
public static bool TryGetDictEnumerator(object dictionary, out IEnumerator<DictionaryEntry> dictEnumerator)
=> Instance.Internal_TryGetDictEnumerator(dictionary, out dictEnumerator);
protected virtual bool Internal_TryGetDictEnumerator(object dictionary, out IEnumerator<DictionaryEntry> dictEnumerator)
{
dictEnumerator = EnumerateDictionary((IDictionary)dictionary);
return true;
}
private IEnumerator<DictionaryEntry> EnumerateDictionary(IDictionary dict)
{
var enumerator = dict.GetEnumerator();
while (enumerator.MoveNext())
{
yield return new DictionaryEntry(enumerator.Key, enumerator.Value);
}
}
// TryGetEntryTypes
public static bool TryGetEntryTypes(Type dictionaryType, out Type keys, out Type values)
=> Instance.Internal_TryGetEntryTypes(dictionaryType, out keys, out values);
protected virtual bool Internal_TryGetEntryTypes(Type dictionaryType, out Type keys, out Type values)
{
foreach (var t in dictionaryType.GetInterfaces())
{
if (t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IDictionary<,>))
{
var args = t.GetGenericArguments();
keys = args[0];
values = args[1];
return true;
}
}
keys = typeof(object);
values = typeof(object);
return false;
}
}
}

View File

@ -1,91 +0,0 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
using UnityExplorer.Core.Runtime.Il2Cpp;
namespace UnityExplorer
{
public class AssetBundle
{
// ~~~~~~~~~~~~ Static ~~~~~~~~~~~~
internal delegate IntPtr d_LoadFromFile(IntPtr path, uint crc, ulong offset);
public static AssetBundle LoadFromFile(string path)
{
var iCall = ICallManager.GetICall<d_LoadFromFile>("UnityEngine.AssetBundle::LoadFromFile_Internal");
var ptr = iCall.Invoke(IL2CPP.ManagedStringToIl2Cpp(path), 0u, 0UL);
return new AssetBundle(ptr);
}
private delegate IntPtr d_LoadFromMemory(IntPtr binary, uint crc);
public static AssetBundle LoadFromMemory(byte[] binary, uint crc = 0)
{
var iCall = ICallManager.GetICall<d_LoadFromMemory>("UnityEngine.AssetBundle::LoadFromMemory_Internal");
var ptr = iCall(((Il2CppStructArray<byte>) binary).Pointer, crc);
return new AssetBundle(ptr);
}
// static void UnloadAllAssetBundles(bool unloadAllObjects);
internal delegate void d_UnloadAllAssetBundles(bool unloadAllObjects);
public static void UnloadAllAssetBundles(bool unloadAllObjects)
{
var iCall = ICallManager.GetICall<d_UnloadAllAssetBundles>("UnityEngine.AssetBundle::UnloadAllAssetBundles");
iCall.Invoke(unloadAllObjects);
}
// ~~~~~~~~~~~~ Instance ~~~~~~~~~~~~
private readonly IntPtr m_bundlePtr = IntPtr.Zero;
public AssetBundle(IntPtr ptr) { m_bundlePtr = ptr; }
// LoadAllAssets()
internal delegate IntPtr d_LoadAssetWithSubAssets_Internal(IntPtr _this, IntPtr name, IntPtr type);
public UnityEngine.Object[] LoadAllAssets()
{
var iCall = ICallManager.GetICall<d_LoadAssetWithSubAssets_Internal>("UnityEngine.AssetBundle::LoadAssetWithSubAssets_Internal");
var ptr = iCall.Invoke(m_bundlePtr, IL2CPP.ManagedStringToIl2Cpp(""), Il2CppType.Of<UnityEngine.Object>().Pointer);
if (ptr == IntPtr.Zero)
return new UnityEngine.Object[0];
return new Il2CppReferenceArray<UnityEngine.Object>(ptr);
}
// LoadAsset<T>(string name, Type type)
internal delegate IntPtr d_LoadAsset_Internal(IntPtr _this, IntPtr name, IntPtr type);
public T LoadAsset<T>(string name) where T : UnityEngine.Object
{
var iCall = ICallManager.GetICall<d_LoadAsset_Internal>("UnityEngine.AssetBundle::LoadAsset_Internal");
var ptr = iCall.Invoke(m_bundlePtr, IL2CPP.ManagedStringToIl2Cpp(name), Il2CppType.Of<T>().Pointer);
if (ptr == IntPtr.Zero)
return null;
return new UnityEngine.Object(ptr).TryCast<T>();
}
// public extern void Unload(bool unloadAllLoadedObjects);
internal delegate void d_Unload(IntPtr _this, bool unloadAllLoadedObjects);
public void Unload(bool unloadAssets = true)
{
var iCall = ICallManager.GetICall<d_Unload>("UnityEngine.AssetBundle::Unload");
iCall.Invoke(this.m_bundlePtr, unloadAssets);
}
}
}
#endif

View File

@ -1,72 +0,0 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using System.Runtime.InteropServices;
namespace UnityExplorer.Core.Runtime.Il2Cpp
{
[SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "External methods")]
public static class ICallManager
{
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr il2cpp_resolve_icall([MarshalAs(UnmanagedType.LPStr)] string name);
private static readonly Dictionary<string, Delegate> iCallCache = new Dictionary<string, Delegate>();
/// <summary>
/// Helper to get and cache an iCall by providing the signature (eg. "UnityEngine.Resources::FindObjectsOfTypeAll").
/// </summary>
/// <typeparam name="T">The Type of Delegate to provide for the iCall.</typeparam>
/// <param name="signature">The signature of the iCall you want to get.</param>
/// <returns>The <typeparamref name="T"/> delegate if successful.</returns>
/// <exception cref="MissingMethodException">If the iCall could not be found.</exception>
public static T GetICall<T>(string signature) where T : Delegate
{
if (iCallCache.ContainsKey(signature))
return (T)iCallCache[signature];
IntPtr ptr = il2cpp_resolve_icall(signature);
if (ptr == IntPtr.Zero)
throw new MissingMethodException($"Could not find any iCall with the signature '{signature}'!");
Delegate iCall = Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
iCallCache.Add(signature, iCall);
return (T)iCall;
}
private static readonly Dictionary<string, Delegate> s_unreliableCache = new Dictionary<string, Delegate>();
/// <summary>
/// Get an iCall which may be one of multiple different signatures (ie, it changed in different Unity versions).
/// Each possible signature must have the same Type pattern, it can only vary by name.
/// </summary>
public static T GetICallUnreliable<T>(IEnumerable<string> possibleSignatures) where T : Delegate
{
// use the first possible signature as the 'key'.
string key = possibleSignatures.First();
if (s_unreliableCache.ContainsKey(key))
return (T)s_unreliableCache[key];
T iCall;
IntPtr ptr;
foreach (var sig in possibleSignatures)
{
ptr = il2cpp_resolve_icall(sig);
if (ptr != IntPtr.Zero)
{
iCall = (T)Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
s_unreliableCache.Add(key, iCall);
return iCall;
}
}
throw new MissingMethodException($"Could not find any iCall from list of provided signatures starting with '{key}'!");
}
}
}
#endif

View File

@ -1,159 +0,0 @@
#if CPP
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnhollowerBaseLib;
using UnityEngine;
// CREDIT HerpDerpenstine
// https://github.com/LavaGang/MelonLoader/blob/master/MelonLoader.Support.Il2Cpp/MelonCoroutines.cs
namespace UnityExplorer.Core.Runtime.Il2Cpp
{
public static class Il2CppCoroutine
{
private struct CoroTuple
{
public object WaitCondition;
public IEnumerator Coroutine;
}
private static readonly List<CoroTuple> ourCoroutinesStore = new List<CoroTuple>();
private static readonly List<IEnumerator> ourNextFrameCoroutines = new List<IEnumerator>();
private static readonly List<IEnumerator> ourWaitForFixedUpdateCoroutines = new List<IEnumerator>();
private static readonly List<IEnumerator> ourWaitForEndOfFrameCoroutines = new List<IEnumerator>();
private static readonly List<IEnumerator> tempList = new List<IEnumerator>();
internal static object Start(IEnumerator routine)
{
if (routine != null) ProcessNextOfCoroutine(routine);
return routine;
}
internal static void Stop(IEnumerator enumerator)
{
if (ourNextFrameCoroutines.Contains(enumerator)) // the coroutine is running itself
ourNextFrameCoroutines.Remove(enumerator);
else
{
int coroTupleIndex = ourCoroutinesStore.FindIndex(c => c.Coroutine == enumerator);
if (coroTupleIndex != -1) // the coroutine is waiting for a subroutine
{
object waitCondition = ourCoroutinesStore[coroTupleIndex].WaitCondition;
if (waitCondition is IEnumerator waitEnumerator)
Stop(waitEnumerator);
ourCoroutinesStore.RemoveAt(coroTupleIndex);
}
}
}
private static void ProcessCoroList(List<IEnumerator> target)
{
if (target.Count == 0) return;
// use a temp list to make sure waits made during processing are not handled by same processing invocation
// additionally, a temp list reduces allocations compared to an array
tempList.AddRange(target);
target.Clear();
foreach (var enumerator in tempList) ProcessNextOfCoroutine(enumerator);
tempList.Clear();
}
internal static void Process()
{
for (var i = ourCoroutinesStore.Count - 1; i >= 0; i--)
{
var tuple = ourCoroutinesStore[i];
if (tuple.WaitCondition is WaitForSeconds waitForSeconds)
{
if ((waitForSeconds.m_Seconds -= Time.deltaTime) <= 0)
{
ourCoroutinesStore.RemoveAt(i);
ProcessNextOfCoroutine(tuple.Coroutine);
}
}
}
ProcessCoroList(ourNextFrameCoroutines);
}
internal static void ProcessWaitForFixedUpdate() => ProcessCoroList(ourWaitForFixedUpdateCoroutines);
internal static void ProcessWaitForEndOfFrame() => ProcessCoroList(ourWaitForEndOfFrameCoroutines);
private static void ProcessNextOfCoroutine(IEnumerator enumerator)
{
try
{
if (!enumerator.MoveNext()) // Run the next step of the coroutine. If it's done, restore the parent routine
{
var indices = ourCoroutinesStore.Select((it, idx) => (idx, it)).Where(it => it.it.WaitCondition == enumerator).Select(it => it.idx).ToList();
for (var i = indices.Count - 1; i >= 0; i--)
{
var index = indices[i];
ourNextFrameCoroutines.Add(ourCoroutinesStore[index].Coroutine);
ourCoroutinesStore.RemoveAt(index);
}
return;
}
}
catch (Exception e)
{
ExplorerCore.LogError(e.ToString());
Stop(FindOriginalCoro(enumerator)); // We want the entire coroutine hierachy to stop when an error happen
}
var next = enumerator.Current;
switch (next)
{
case null:
ourNextFrameCoroutines.Add(enumerator);
return;
case WaitForFixedUpdate _:
ourWaitForFixedUpdateCoroutines.Add(enumerator);
return;
case WaitForEndOfFrame _:
ourWaitForEndOfFrameCoroutines.Add(enumerator);
return;
case WaitForSeconds _:
break; // do nothing, this one is supported in Process
case Il2CppObjectBase il2CppObjectBase:
var nextAsEnumerator = il2CppObjectBase.TryCast<Il2CppSystem.Collections.IEnumerator>();
if (nextAsEnumerator != null) // il2cpp IEnumerator also handles CustomYieldInstruction
next = new Il2CppEnumeratorWrapper(nextAsEnumerator);
else
ExplorerCore.LogWarning($"Unknown coroutine yield object of type '{il2CppObjectBase}' for coroutine '{enumerator}'");
return;
default:
ExplorerCore.LogWarning($"Unknown coroutine yield object of type '{next}' for coroutine '{enumerator}'");
return;
}
ourCoroutinesStore.Add(new CoroTuple { WaitCondition = next, Coroutine = enumerator });
if (next is IEnumerator nextCoro)
ProcessNextOfCoroutine(nextCoro);
}
private static IEnumerator FindOriginalCoro(IEnumerator enumerator)
{
int index = ourCoroutinesStore.FindIndex(ct => ct.WaitCondition == enumerator);
if (index == -1)
return enumerator;
return FindOriginalCoro(ourCoroutinesStore[index].Coroutine);
}
private class Il2CppEnumeratorWrapper : IEnumerator
{
private readonly Il2CppSystem.Collections.IEnumerator il2cppEnumerator;
public Il2CppEnumeratorWrapper(Il2CppSystem.Collections.IEnumerator il2CppEnumerator) => il2cppEnumerator = il2CppEnumerator;
public bool MoveNext() => il2cppEnumerator.MoveNext();
public void Reset() => il2cppEnumerator.Reset();
public object Current => il2cppEnumerator.Current;
}
}
}
#endif

View File

@ -1,265 +0,0 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using BF = System.Reflection.BindingFlags;
using System.Text;
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using System.Collections;
using UnityEngine.UI;
using UnityExplorer.Core.Input;
using UnityEngine.EventSystems;
namespace UnityExplorer.Core.Runtime.Il2Cpp
{
public class Il2CppProvider : RuntimeProvider
{
public override void Initialize()
{
ExplorerCore.Context = RuntimeContext.IL2CPP;
TextureUtil = new Il2CppTextureUtil();
}
public override void SetupEvents()
{
try
{
Application.add_logMessageReceived(new Action<string, string, LogType>(Application_logMessageReceived));
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception setting up Unity log listener, make sure Unity libraries have been unstripped!");
ExplorerCore.Log(ex);
}
}
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
{
ExplorerCore.LogUnity(condition, type);
}
public override void StartCoroutine(IEnumerator routine)
{
Il2CppCoroutine.Start(routine);
}
internal override void ProcessOnPostRender()
{
Il2CppCoroutine.ProcessWaitForEndOfFrame();
}
public override void Update()
{
Il2CppCoroutine.Process();
}
public override T AddComponent<T>(GameObject obj, Type type)
{
return obj.AddComponent(Il2CppType.From(type)).TryCast<T>();
}
public override ScriptableObject CreateScriptable(Type type)
{
return ScriptableObject.CreateInstance(Il2CppType.From(type));
}
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
{
var il2cppList = new Il2CppSystem.Collections.Generic.List<RaycastResult>();
raycaster.Raycast(data, il2cppList);
if (il2cppList.Count > 0)
list.AddRange(il2cppList.ToArray());
}
// LayerMask.LayerToName
internal delegate IntPtr d_LayerToName(int layer);
public override string LayerToName(int layer)
{
var iCall = ICallManager.GetICall<d_LayerToName>("UnityEngine.LayerMask::LayerToName");
return IL2CPP.Il2CppStringToManaged(iCall.Invoke(layer));
}
// Resources.FindObjectsOfTypeAll
internal delegate IntPtr d_FindObjectsOfTypeAll(IntPtr type);
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
{
var iCall = ICallManager.GetICallUnreliable<d_FindObjectsOfTypeAll>(new[]
{
"UnityEngine.Resources::FindObjectsOfTypeAll",
"UnityEngine.ResourcesAPIInternal::FindObjectsOfTypeAll" // Unity 2020+ updated to this
});
return new Il2CppReferenceArray<UnityEngine.Object>(iCall.Invoke(Il2CppType.From(type).Pointer));
}
// Scene.GetRootGameObjects();
internal delegate void d_GetRootGameObjects(int handle, IntPtr list);
public override GameObject[] GetRootGameObjects(Scene scene)
{
if (!scene.isLoaded)
return new GameObject[0];
int handle = scene.handle;
if (handle == -1)
return new GameObject[0];
int count = GetRootCount(handle);
if (count < 1)
return new GameObject[0];
var list = new Il2CppSystem.Collections.Generic.List<GameObject>(count);
var iCall = ICallManager.GetICall<d_GetRootGameObjects>("UnityEngine.SceneManagement.Scene::GetRootGameObjectsInternal");
iCall.Invoke(handle, list.Pointer);
return list.ToArray();
}
// Scene.rootCount
internal delegate int d_GetRootCountInternal(int handle);
public override int GetRootCount(Scene scene) => GetRootCount(scene.handle);
public static int GetRootCount(int handle)
{
return ICallManager.GetICall<d_GetRootCountInternal>("UnityEngine.SceneManagement.Scene::GetRootCountInternal")
.Invoke(handle);
}
internal static bool triedToGetColorBlockProps;
internal static PropertyInfo _normalColorProp;
internal static PropertyInfo _highlightColorProp;
internal static PropertyInfo _pressedColorProp;
internal static PropertyInfo _disabledColorProp;
public override void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
Color? disabled = null)
{
var colors = selectable.colors;
colors.colorMultiplier = 1;
object boxed = (object)colors;
if (!triedToGetColorBlockProps)
{
triedToGetColorBlockProps = true;
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "normalColor") is PropertyInfo norm && norm.CanWrite)
_normalColorProp = norm;
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "highlightedColor") is PropertyInfo high && high.CanWrite)
_highlightColorProp = high;
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "pressedColor") is PropertyInfo pres && pres.CanWrite)
_pressedColorProp = pres;
if (ReflectionUtility.GetPropertyInfo(typeof(ColorBlock), "disabledColor") is PropertyInfo disa && disa.CanWrite)
_disabledColorProp = disa;
}
try
{
if (normal != null)
{
if (_normalColorProp != null)
_normalColorProp.SetValue(boxed, (Color)normal);
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_NormalColor") is FieldInfo fi)
fi.SetValue(boxed, (Color)normal);
}
if (highlighted != null)
{
if (_highlightColorProp != null)
_highlightColorProp.SetValue(boxed, (Color)highlighted);
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_HighlightedColor") is FieldInfo fi)
fi.SetValue(boxed, (Color)highlighted);
}
if (pressed != null)
{
if (_pressedColorProp != null)
_pressedColorProp.SetValue(boxed, (Color)pressed);
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_PressedColor") is FieldInfo fi)
fi.SetValue(boxed, (Color)pressed);
}
if (disabled != null)
{
if (_disabledColorProp != null)
_disabledColorProp.SetValue(boxed, (Color)disabled);
else if (ReflectionUtility.GetFieldInfo(typeof(ColorBlock), "m_DisabledColor") is FieldInfo fi)
fi.SetValue(boxed, (Color)disabled);
}
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
colors = (ColorBlock)boxed;
SetColorBlock(selectable, colors);
}
public override void SetColorBlock(Selectable selectable, ColorBlock _colorBlock)
{
try
{
selectable = selectable.TryCast<Selectable>();
ReflectionUtility.GetPropertyInfo(typeof(Selectable), "m_Colors")
.SetValue(selectable, _colorBlock, null);
ReflectionUtility.GetMethodInfo(typeof(Selectable), "OnSetProperty")
.Invoke(selectable, ArgumentUtility.EmptyArgs);
}
catch (Exception ex)
{
ExplorerCore.Log(ex);
}
}
}
}
public static class Il2CppExtensions
{
public static void AddListener(this UnityEvent action, Action listener)
{
action.AddListener(listener);
}
public static void AddListener<T>(this UnityEvent<T> action, Action<T> listener)
{
action.AddListener(listener);
}
public static void RemoveListener(this UnityEvent action, Action listener)
{
action.RemoveListener(listener);
}
public static void RemoveListener<T>(this UnityEvent<T> action, Action<T> listener)
{
action.RemoveListener(listener);
}
public static void SetChildControlHeight(this HorizontalOrVerticalLayoutGroup group, bool value) => group.childControlHeight = value;
public static void SetChildControlWidth(this HorizontalOrVerticalLayoutGroup group, bool value) => group.childControlWidth = value;
}
#endif

View File

@ -1,77 +0,0 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using UnhollowerBaseLib;
using UnityEngine;
namespace UnityExplorer.Core.Runtime.Il2Cpp
{
public class Il2CppTextureUtil : TextureUtilProvider
{
public override Texture2D NewTexture2D(int width, int height)
=> new Texture2D((int)width, (int)height, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero);
internal delegate void d_Blit2(IntPtr source, IntPtr dest);
public override void Blit(Texture2D tex, RenderTexture rt)
{
var iCall = ICallManager.GetICall<d_Blit2>("UnityEngine.Graphics::Blit2");
iCall.Invoke(tex.Pointer, rt.Pointer);
}
// byte[] ImageConversion.EncodeToPNG(this Texture2D image);
internal delegate IntPtr d_EncodeToPNG(IntPtr tex);
public override byte[] EncodeToPNG(Texture2D tex)
{
var iCall = ICallManager.GetICall<d_EncodeToPNG>("UnityEngine.ImageConversion::EncodeToPNG");
IntPtr ptr = iCall.Invoke(tex.Pointer);
if (ptr == IntPtr.Zero)
return null;
return new Il2CppStructArray<byte>(ptr);
}
// bool ImageConversion.LoadImage(this Texture2D tex, byte[] data, bool markNonReadable);
internal delegate bool d_LoadImage(IntPtr tex, IntPtr data, bool markNonReadable);
public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
{
var il2cppArray = (Il2CppStructArray<byte>)data;
var iCall = ICallManager.GetICall<d_LoadImage>("UnityEngine.ImageConversion::LoadImage");
return iCall.Invoke(tex.Pointer, il2cppArray.Pointer, markNonReadable);
}
// Sprite Sprite.Create
public override Sprite CreateSprite(Texture2D texture)
{
return CreateSpriteImpl(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero, 100f, 0u, Vector4.zero);
}
internal delegate IntPtr d_CreateSprite(IntPtr texture, ref Rect rect, ref Vector2 pivot, float pixelsPerUnit,
uint extrude, int meshType, ref Vector4 border, bool generateFallbackPhysicsShape);
public static Sprite CreateSpriteImpl(Texture texture, Rect rect, Vector2 pivot, float pixelsPerUnit, uint extrude, Vector4 border)
{
var iCall = ICallManager.GetICall<d_CreateSprite>("UnityEngine.Sprite::CreateSprite_Injected");
var ptr = iCall.Invoke(texture.Pointer, ref rect, ref pivot, pixelsPerUnit, extrude, 1, ref border, false);
if (ptr == IntPtr.Zero)
return null;
else
return new Sprite(ptr);
}
}
}
#endif

View File

@ -1,161 +0,0 @@
#if MONO
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
namespace UnityExplorer.Core.Runtime.Mono
{
public class MonoProvider : RuntimeProvider
{
public override void Initialize()
{
ExplorerCore.Context = RuntimeContext.Mono;
//Reflection = new MonoReflection();
TextureUtil = new MonoTextureUtil();
}
public override void SetupEvents()
{
Application.logMessageReceived += Application_logMessageReceived;
}
private void Application_logMessageReceived(string condition, string stackTrace, LogType type)
{
ExplorerCore.LogUnity(condition, type);
}
public override void StartCoroutine(IEnumerator routine)
{
ExplorerBehaviour.Instance.StartCoroutine(routine);
}
public override void Update()
{
}
public override T AddComponent<T>(GameObject obj, Type type)
{
return (T)obj.AddComponent(type);
}
public override ScriptableObject CreateScriptable(Type type)
{
return ScriptableObject.CreateInstance(type);
}
public override void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list)
{
raycaster.Raycast(data, list);
}
public override string LayerToName(int layer)
=> LayerMask.LayerToName(layer);
public override UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
=> Resources.FindObjectsOfTypeAll(type);
//private static readonly FieldInfo fi_Scene_handle = typeof(Scene).GetField("m_Handle", ReflectionUtility.AllFlags);
//public override int GetSceneHandle(Scene scene)
//{
// return (int)fi_Scene_handle.GetValue(scene);
//}
public override GameObject[] GetRootGameObjects(Scene scene)
{
if (!scene.isLoaded)
return new GameObject[0];
return scene.GetRootGameObjects();
}
public override int GetRootCount(Scene scene)
{
return scene.rootCount;
}
public override void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
Color? disabled = null)
{
var colors = selectable.colors;
if (normal != null)
colors.normalColor = (Color)normal;
if (highlighted != null)
colors.highlightedColor = (Color)highlighted;
if (pressed != null)
colors.pressedColor = (Color)pressed;
if (disabled != null)
colors.disabledColor = (Color)disabled;
SetColorBlock(selectable, colors);
}
public override void SetColorBlock(Selectable selectable, ColorBlock colors)
{
selectable.colors = colors;
}
}
}
public static class MonoExtensions
{
public static void AddListener(this UnityEvent _event, Action listener)
{
_event.AddListener(new UnityAction(listener));
}
public static void AddListener<T>(this UnityEvent<T> _event, Action<T> listener)
{
_event.AddListener(new UnityAction<T>(listener));
}
public static void RemoveListener(this UnityEvent _event, Action listener)
{
_event.RemoveListener(new UnityAction(listener));
}
public static void RemoveListener<T>(this UnityEvent<T> _event, Action<T> listener)
{
_event.RemoveListener(new UnityAction<T>(listener));
}
public static void Clear(this StringBuilder sb)
{
sb.Remove(0, sb.Length);
}
private static PropertyInfo pi_childControlHeight;
public static void SetChildControlHeight(this HorizontalOrVerticalLayoutGroup group, bool value)
{
if (pi_childControlHeight == null)
pi_childControlHeight = group.GetType().GetProperty("childControlHeight");
pi_childControlHeight?.SetValue(group, value, null);
}
private static PropertyInfo pi_childControlWidth;
public static void SetChildControlWidth(this HorizontalOrVerticalLayoutGroup group, bool value)
{
if (pi_childControlWidth == null)
pi_childControlWidth = group.GetType().GetProperty("childControlWidth");
pi_childControlWidth?.SetValue(group, value, null);
}
}
#endif

View File

@ -1,66 +0,0 @@
#if MONO
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityExplorer.Core;
namespace UnityExplorer.Core.Runtime.Mono
{
public class MonoTextureUtil : TextureUtilProvider
{
public override void Blit(Texture2D tex, RenderTexture rt)
{
Graphics.Blit(tex, rt);
}
public override Sprite CreateSprite(Texture2D texture)
{
return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
}
public override bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable)
{
return tex.LoadImage(data, markNonReadable);
}
public override Texture2D NewTexture2D(int width, int height)
{
return new Texture2D(width, height);
}
public override byte[] EncodeToPNG(Texture2D tex)
{
return EncodeToPNGSafe(tex);
}
private static MethodInfo EncodeToPNGMethod => m_encodeToPNGMethod ?? GetEncodeToPNGMethod();
private static MethodInfo m_encodeToPNGMethod;
public static byte[] EncodeToPNGSafe(Texture2D tex)
{
var method = EncodeToPNGMethod;
if (method.IsStatic)
return (byte[])method.Invoke(null, new object[] { tex });
else
return (byte[])method.Invoke(tex, ArgumentUtility.EmptyArgs);
}
private static MethodInfo GetEncodeToPNGMethod()
{
if (ReflectionUtility.GetTypeByName("UnityEngine.ImageConversion") is Type imageConversion)
return m_encodeToPNGMethod = imageConversion.GetMethod("EncodeToPNG", ReflectionUtility.FLAGS);
var method = typeof(Texture2D).GetMethod("EncodeToPNG", ReflectionUtility.FLAGS);
if (method != null)
return m_encodeToPNGMethod = method;
ExplorerCore.Log("ERROR: Cannot get any EncodeToPNG method!");
return null;
}
}
}
#endif

View File

@ -1,13 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer.Core.Runtime
{
public enum RuntimeContext
{
Mono,
IL2CPP
}
}

View File

@ -1,76 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
using UnityExplorer.Core.Runtime;
// Intentionally project-wide namespace so that its always easily accessible.
namespace UnityExplorer
{
public abstract class RuntimeProvider
{
public static RuntimeProvider Instance;
public TextureUtilProvider TextureUtil;
public RuntimeProvider()
{
Initialize();
SetupEvents();
}
public static void Init() =>
#if CPP
Instance = new Core.Runtime.Il2Cpp.Il2CppProvider();
#else
Instance = new Core.Runtime.Mono.MonoProvider();
#endif
public abstract void Initialize();
public abstract void SetupEvents();
public abstract void StartCoroutine(IEnumerator routine);
public abstract void Update();
//public virtual bool IsReferenceEqual(object a, object b) => ReferenceEquals(a, b);
// Unity API handlers
public abstract T AddComponent<T>(GameObject obj, Type type) where T : Component;
public abstract ScriptableObject CreateScriptable(Type type);
public abstract string LayerToName(int layer);
public abstract UnityEngine.Object[] FindObjectsOfTypeAll(Type type);
public abstract void GraphicRaycast(GraphicRaycaster raycaster, PointerEventData data, List<RaycastResult> list);
//public abstract int GetSceneHandle(Scene scene);
public abstract GameObject[] GetRootGameObjects(Scene scene);
public abstract int GetRootCount(Scene scene);
public abstract void SetColorBlock(Selectable selectable, ColorBlock colors);
public abstract void SetColorBlock(Selectable selectable, Color? normal = null, Color? highlighted = null, Color? pressed = null,
Color? disabled = null);
internal virtual void ProcessOnPostRender()
{
}
internal virtual void ProcessFixedUpdate()
{
}
}
}

View File

@ -1,160 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using UnityEngine;
namespace UnityExplorer.Core.Runtime
{
public abstract class TextureUtilProvider
{
public static TextureUtilProvider Instance;
public TextureUtilProvider()
{
Instance = this;
}
public abstract byte[] EncodeToPNG(Texture2D tex);
public abstract Texture2D NewTexture2D(int width, int height);
public abstract void Blit(Texture2D tex, RenderTexture rt);
public abstract bool LoadImage(Texture2D tex, byte[] data, bool markNonReadable);
public abstract Sprite CreateSprite(Texture2D texture);
public static bool IsReadable(Texture2D tex)
{
try
{
// This will cause an exception if it's not readable.
// Reason for doing it this way is not all Unity versions
// ship with the 'Texture.isReadable' property.
tex.GetPixel(0, 0);
return true;
}
catch
{
return false;
}
}
public static bool LoadImage(Texture2D tex, string filePath, bool markNonReadable)
{
if (!File.Exists(filePath))
return false;
return Instance.LoadImage(tex, File.ReadAllBytes(filePath), markNonReadable);
}
public static Texture2D Copy(Texture2D orig, Rect rect)
{
Color[] pixels;
if (!IsReadable(orig))
orig = ForceReadTexture(orig);
pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
Texture2D newTex = Instance.NewTexture2D((int)rect.width, (int)rect.height);
newTex.SetPixels(pixels);
return newTex;
}
public static Texture2D ForceReadTexture(Texture2D tex)
{
try
{
FilterMode origFilter = tex.filterMode;
tex.filterMode = FilterMode.Point;
var rt = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.ARGB32);
rt.filterMode = FilterMode.Point;
RenderTexture.active = rt;
Instance.Blit(tex, rt);
var _newTex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false);
_newTex.ReadPixels(new Rect(0, 0, tex.width, tex.height), 0, 0);
_newTex.Apply(false, false);
RenderTexture.active = null;
tex.filterMode = origFilter;
return _newTex;
}
catch (Exception e)
{
ExplorerCore.Log("Exception on ForceReadTexture: " + e.ToString());
return default;
}
}
public static void SaveTextureAsPNG(Texture2D tex, string dir, string name, bool isDTXnmNormal = false)
{
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
byte[] data;
string savepath = dir + @"\" + name + ".png";
// Make sure we can EncodeToPNG it.
if (tex.format != TextureFormat.ARGB32 || !IsReadable(tex))
{
tex = ForceReadTexture(tex);
}
if (isDTXnmNormal)
{
tex = DTXnmToRGBA(tex);
tex.Apply(false, false);
}
data = Instance.EncodeToPNG(tex);
if (data == null || !data.Any())
{
ExplorerCore.LogWarning("Couldn't get any data for the texture!");
}
else
{
File.WriteAllBytes(savepath, data);
}
}
// Converts DTXnm-format Normal Map to RGBA-format Normal Map.
public static Texture2D DTXnmToRGBA(Texture2D tex)
{
Color[] colors = tex.GetPixels();
for (int i = 0; i < colors.Length; i++)
{
var c = colors[i];
c.r = c.a * 2 - 1; // red <- alpha
c.g = c.g * 2 - 1; // green is always the same
var rg = new Vector2(c.r, c.g); //this is the red-green vector
c.b = Mathf.Sqrt(1 - Mathf.Clamp01(Vector2.Dot(rg, rg))); //recalculate the blue channel
colors[i] = new Color(
(c.r * 0.5f) + 0.5f,
(c.g * 0.5f) + 0.25f,
(c.b * 0.5f) + 0.5f
);
}
var newtex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false);
newtex.SetPixels(colors);
return newtex;
}
}
}

View File

@ -1,195 +0,0 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace UnityExplorer.Core
{
public static class SceneHandler
{
/// <summary>
/// The currently inspected Scene.
/// </summary>
public static Scene? SelectedScene
{
get => m_selectedScene;
internal set
{
if (m_selectedScene != null && m_selectedScene?.handle == value?.handle)
return;
m_selectedScene = value;
OnInspectedSceneChanged?.Invoke((Scene)m_selectedScene);
}
}
private static Scene? m_selectedScene;
/// <summary>
/// The GameObjects in the currently inspected scene.
/// </summary>
public static ReadOnlyCollection<GameObject> CurrentRootObjects => new ReadOnlyCollection<GameObject>(rootObjects);
private static GameObject[] rootObjects = new GameObject[0];
/// <summary>
/// All currently loaded Scenes.
/// </summary>
public static ReadOnlyCollection<Scene> LoadedScenes => new ReadOnlyCollection<Scene>(allLoadedScenes);
private static readonly List<Scene> allLoadedScenes = new List<Scene>();
/// <summary>
/// The names of all scenes in the build settings, if they could be retrieved.
/// </summary>
public static ReadOnlyCollection<string> AllSceneNames => new ReadOnlyCollection<string>(allScenesInBuild);
private static readonly List<string> allScenesInBuild = new List<string>();
/// <summary>
/// Whether or not we successfuly retrieved the names of the scenes in the build settings.
/// </summary>
public static bool WasAbleToGetScenesInBuild => gotAllScenesInBuild;
private static bool gotAllScenesInBuild = true;
/// <summary>
/// Invoked when the currently inspected Scene changes. The argument is the new scene.
/// </summary>
public static event Action<Scene> OnInspectedSceneChanged;
/// <summary>
/// Invoked whenever the list of currently loaded Scenes changes. The argument contains all loaded scenes after the change.
/// </summary>
public static event Action<ReadOnlyCollection<Scene>> OnLoadedScenesChanged;
/// <summary>
/// Equivalent to <see cref="SceneManager.sceneCount"/> + 2, to include 'DontDestroyOnLoad'.
/// </summary>
public static int LoadedSceneCount => SceneManager.sceneCount + 2;
internal static Scene DontDestroyScene => DontDestroyMe.scene;
internal static int DontDestroyHandle => DontDestroyScene.handle;
internal static GameObject DontDestroyMe
{
get
{
if (!dontDestroyObject)
{
dontDestroyObject = new GameObject("DontDestroyMe");
GameObject.DontDestroyOnLoad(dontDestroyObject);
}
return dontDestroyObject;
}
}
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;
internal static void Init()
{
// Try to get all scenes in the build settings. This may not work.
try
{
Type sceneUtil = ReflectionUtility.GetTypeByName("UnityEngine.SceneManagement.SceneUtility");
if (sceneUtil == null)
throw new Exception("This version of Unity does not ship with the 'SceneUtility' class, or it was not unstripped.");
var method = sceneUtil.GetMethod("GetScenePathByBuildIndex", ReflectionUtility.FLAGS);
int sceneCount = SceneManager.sceneCountInBuildSettings;
for (int i = 0; i < sceneCount; i++)
{
var scenePath = (string)method.Invoke(null, new object[] { i });
allScenesInBuild.Add(scenePath);
}
}
catch (Exception ex)
{
gotAllScenesInBuild = false;
ExplorerCore.Log($"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));
allLoadedScenes.Clear();
for (int i = 0; i < SceneManager.sceneCount; i++)
{
Scene scene = SceneManager.GetSceneAt(i);
if (scene == default || scene.handle == -1 || !scene.isLoaded)
continue;
// If no changes yet, ensure the previous list contained this handle.
if (!anyChange && !previousHandles.Contains(scene.handle))
anyChange = true;
// If we have not yet confirmed inspectedExists, check if this scene is our currently inspected one.
if (curHandle != -1 && !inspectedExists && scene.handle == curHandle)
inspectedExists = true;
allLoadedScenes.Add(scene);
}
// Always add the DontDestroyOnLoad scene and the "none" scene.
allLoadedScenes.Add(DontDestroyScene);
allLoadedScenes.Add(AssetScene);
// Default to first scene if none selected or previous selection no longer exists.
if (!inspectedExists)
{
SelectedScene = allLoadedScenes.First();
}
// Notify on the list changing at all
if (anyChange)
{
OnLoadedScenesChanged?.Invoke(LoadedScenes);
}
// Finally, update the root objects list.
if (SelectedScene != null && ((Scene)SelectedScene).IsValid())
rootObjects = RuntimeProvider.Instance.GetRootGameObjects((Scene)SelectedScene);
else
{
var allObjects = RuntimeProvider.Instance.FindObjectsOfTypeAll(typeof(GameObject));
var list = new List<GameObject>();
foreach (var obj in allObjects)
{
var go = obj.TryCast<GameObject>();
if (go.transform.parent == null && !go.scene.IsValid())
list.Add(go);
}
rootObjects = list.ToArray();
}
}
}
}

View File

@ -1,15 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer
{
public static class ArgumentUtility
{
public static readonly Type[] EmptyTypes = new Type[0];
public static readonly object[] EmptyArgs = new object[0];
public static readonly Type[] ParseArgs = new Type[] { typeof(string) };
}
}

View File

@ -1,29 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace UnityExplorer
{
public static class IOUtility
{
private static readonly char[] invalidDirectoryCharacters = Path.GetInvalidPathChars();
private static readonly char[] invalidFilenameCharacters = Path.GetInvalidFileNameChars();
public static string EnsureValidDirectory(string path)
{
path = string.Concat(path.Split(invalidDirectoryCharacters));
if (!Directory.Exists(path))
Directory.CreateDirectory(path);
return path;
}
public static string EnsureValidFilename(string filename)
{
return string.Concat(filename.Split(invalidFilenameCharacters));
}
}
}

View File

@ -1,28 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
namespace UnityExplorer
{
public static class MiscUtility
{
/// <summary>
/// Check if a string contains another string, case-insensitive.
/// </summary>
public static bool ContainsIgnoreCase(this string _this, string s)
{
return ParseUtility.en_US.CompareInfo.IndexOf(_this, s, CompareOptions.IgnoreCase) >= 0;
}
/// <summary>
/// Just to allow Enum to do .HasFlag() in NET 3.5
/// </summary>
public static bool HasFlag(this Enum flags, Enum value)
{
ulong flag = Convert.ToUInt64(value);
return (Convert.ToUInt64(flags) & flag) == flag;
}
}
}

View File

@ -1,420 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
namespace UnityExplorer
{
public static class ParseUtility
{
public static CultureInfo en_US = new CultureInfo("en-US");
private static readonly HashSet<Type> nonPrimitiveTypes = new HashSet<Type>
{
typeof(string),
typeof(decimal),
typeof(DateTime),
};
public const string NUMBER_FORMAT = "0.####";
private static readonly Dictionary<int, string> numSequenceStrings = new Dictionary<int, string>();
// Helper for formatting float/double/decimal numbers to maximum of 4 decimal points.
public static string FormatDecimalSequence(params object[] numbers)
{
if (numbers.Length <= 0)
return null;
int count = numbers.Length;
var formatString = GetSequenceFormatString(count);
return string.Format(en_US, formatString, numbers);
}
public static string GetSequenceFormatString(int count)
{
if (count <= 0)
return null;
if (numSequenceStrings.ContainsKey(count))
return numSequenceStrings[count];
string[] strings = new string[count];
for (int i = 0; i < count; i++)
strings[i] = $"{{{i}:{NUMBER_FORMAT}}}";
string s = string.Join(", ", strings);
numSequenceStrings.Add(count, s);
return s;
}
public static bool CanParse(Type type)
{
if (string.IsNullOrEmpty(type.FullName))
return false;
return type.IsPrimitive || type.IsEnum || nonPrimitiveTypes.Contains(type) || customTypes.ContainsKey(type.FullName);
}
public static bool TryParse(string input, Type type, out object obj, out Exception parseException)
{
obj = null;
parseException = null;
if (type == null)
return false;
if (type == typeof(string))
{
obj = input;
return true;
}
if (type.IsEnum)
{
try
{
obj = Enum.Parse(type, input);
return true;
}
catch (Exception ex)
{
parseException = ex.GetInnerMostException();
return false;
}
}
try
{
if (customTypes.ContainsKey(type.FullName))
{
obj = customTypes[type.FullName].Invoke(input);
}
else
{
obj = ReflectionUtility.GetMethodInfo(type, "Parse", ArgumentUtility.ParseArgs)
.Invoke(null, new object[] { input });
}
return true;
}
catch (Exception ex)
{
ex = ex.GetInnerMostException();
parseException = ex;
}
return false;
}
private static readonly HashSet<Type> formattedTypes = new HashSet<Type>
{
typeof(float),
typeof(double),
typeof(decimal)
};
public static string ToStringForInput(object obj, Type type)
{
if (type == null || obj == null)
return null;
if (type == typeof(string))
return obj as string;
if (type.IsEnum)
{
return Enum.IsDefined(type, obj)
? Enum.GetName(type, obj)
: obj.ToString();
}
try
{
if (customTypes.ContainsKey(type.FullName))
{
return customTypesToString[type.FullName].Invoke(obj);
}
else if (formattedTypes.Contains(type))
{
return ReflectionUtility.GetMethodInfo(type, "ToString", new Type[] { typeof(string), typeof(IFormatProvider) })
.Invoke(obj, new object[] { NUMBER_FORMAT, en_US })
as string;
}
else
return obj.ToString();
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception formatting object for input: {ex}");
return null;
}
}
private static readonly Dictionary<string, string> typeInputExamples = new Dictionary<string, string>();
public static string GetExampleInput(Type type)
{
if (!typeInputExamples.ContainsKey(type.AssemblyQualifiedName))
{
try
{
if (type.IsEnum)
{
typeInputExamples.Add(type.AssemblyQualifiedName, Enum.GetNames(type).First());
}
else
{
var instance = Activator.CreateInstance(type);
typeInputExamples.Add(type.AssemblyQualifiedName, ToStringForInput(instance, type));
}
}
catch (Exception ex)
{
ExplorerCore.LogWarning("Exception generating default instance for example input for '" + type.FullName + "'");
ExplorerCore.Log(ex);
return "";
}
}
return typeInputExamples[type.AssemblyQualifiedName];
}
#region Custom parse methods
internal delegate object ParseMethod(string input);
private static readonly Dictionary<string, ParseMethod> customTypes = new Dictionary<string, ParseMethod>
{
{ typeof(Vector2).FullName, TryParseVector2 },
{ typeof(Vector3).FullName, TryParseVector3 },
{ typeof(Vector4).FullName, TryParseVector4 },
{ typeof(Quaternion).FullName, TryParseQuaternion },
{ typeof(Rect).FullName, TryParseRect },
{ typeof(Color).FullName, TryParseColor },
{ typeof(Color32).FullName, TryParseColor32 },
{ typeof(LayerMask).FullName, TryParseLayerMask },
};
internal delegate string ToStringMethod(object obj);
private static readonly Dictionary<string, ToStringMethod> customTypesToString = new Dictionary<string, ToStringMethod>
{
{ typeof(Vector2).FullName, Vector2ToString },
{ typeof(Vector3).FullName, Vector3ToString },
{ typeof(Vector4).FullName, Vector4ToString },
{ typeof(Quaternion).FullName, QuaternionToString },
{ typeof(Rect).FullName, RectToString },
{ typeof(Color).FullName, ColorToString },
{ typeof(Color32).FullName, Color32ToString },
{ typeof(LayerMask).FullName, LayerMaskToString },
};
// Vector2
public static object TryParseVector2(string input)
{
Vector2 vector = default;
var split = input.Split(',');
vector.x = float.Parse(split[0].Trim(), en_US);
vector.y = float.Parse(split[1].Trim(), en_US);
return vector;
}
public static string Vector2ToString(object obj)
{
if (!(obj is Vector2 vector))
return null;
return FormatDecimalSequence(vector.x, vector.y);
}
// Vector3
public static object TryParseVector3(string input)
{
Vector3 vector = default;
var split = input.Split(',');
vector.x = float.Parse(split[0].Trim(), en_US);
vector.y = float.Parse(split[1].Trim(), en_US);
vector.z = float.Parse(split[2].Trim(), en_US);
return vector;
}
public static string Vector3ToString(object obj)
{
if (!(obj is Vector3 vector))
return null;
return FormatDecimalSequence(vector.x, vector.y, vector.z);
}
// Vector4
public static object TryParseVector4(string input)
{
Vector4 vector = default;
var split = input.Split(',');
vector.x = float.Parse(split[0].Trim(), en_US);
vector.y = float.Parse(split[1].Trim(), en_US);
vector.z = float.Parse(split[2].Trim(), en_US);
vector.w = float.Parse(split[3].Trim(), en_US);
return vector;
}
public static string Vector4ToString(object obj)
{
if (!(obj is Vector4 vector))
return null;
return FormatDecimalSequence(vector.x, vector.y, vector.z, vector.w);
}
// Quaternion
public static object TryParseQuaternion(string input)
{
Vector3 vector = default;
var split = input.Split(',');
if (split.Length == 4)
{
Quaternion quat = default;
quat.x = float.Parse(split[0].Trim(), en_US);
quat.y = float.Parse(split[1].Trim(), en_US);
quat.z = float.Parse(split[2].Trim(), en_US);
quat.w = float.Parse(split[3].Trim(), en_US);
return quat;
}
else
{
vector.x = float.Parse(split[0].Trim(), en_US);
vector.y = float.Parse(split[1].Trim(), en_US);
vector.z = float.Parse(split[2].Trim(), en_US);
return Quaternion.Euler(vector);
}
}
public static string QuaternionToString(object obj)
{
if (!(obj is Quaternion quaternion))
return null;
Vector3 vector = quaternion.eulerAngles;
return FormatDecimalSequence(vector.x, vector.y, vector.z);
}
// Rect
public static object TryParseRect(string input)
{
Rect rect = default;
var split = input.Split(',');
rect.x = float.Parse(split[0].Trim(), en_US);
rect.y = float.Parse(split[1].Trim(), en_US);
rect.width = float.Parse(split[2].Trim(), en_US);
rect.height = float.Parse(split[3].Trim(), en_US);
return rect;
}
public static string RectToString(object obj)
{
if (!(obj is Rect rect))
return null;
return FormatDecimalSequence(rect.x, rect.y, rect.width, rect.height);
}
// Color
public static object TryParseColor(string input)
{
Color color = default;
var split = input.Split(',');
color.r = float.Parse(split[0].Trim(), en_US);
color.g = float.Parse(split[1].Trim(), en_US);
color.b = float.Parse(split[2].Trim(), en_US);
if (split.Length > 3)
color.a = float.Parse(split[3].Trim(), en_US);
else
color.a = 1;
return color;
}
public static string ColorToString(object obj)
{
if (!(obj is Color color))
return null;
return FormatDecimalSequence(color.r, color.g, color.b, color.a);
}
// Color32
public static object TryParseColor32(string input)
{
Color32 color = default;
var split = input.Split(',');
color.r = byte.Parse(split[0].Trim(), en_US);
color.g = byte.Parse(split[1].Trim(), en_US);
color.b = byte.Parse(split[2].Trim(), en_US);
if (split.Length > 3)
color.a = byte.Parse(split[3].Trim(), en_US);
else
color.a = 255;
return color;
}
public static string Color32ToString(object obj)
{
if (!(obj is Color32 color))
return null;
// ints, this is fine
return $"{color.r}, {color.g}, {color.b}, {color.a}";
}
// Layermask (Int32)
public static object TryParseLayerMask(string input)
{
return (LayerMask)int.Parse(input);
}
public static string LayerMaskToString(object obj)
{
if (!(obj is LayerMask mask))
return null;
return mask.value.ToString();
}
#endregion
}
}

View File

@ -1,294 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityExplorer.Core.Runtime;
namespace UnityExplorer
{
/// <summary>
/// Syntax-highlights a member's signature, by either the Type name or a Type and Member together.
/// </summary>
public static class SignatureHighlighter
{
public const string NAMESPACE = "#a8a8a8";
public const string CONST = "#92c470";
public const string CLASS_STATIC = "#3a8d71";
public const string CLASS_INSTANCE = "#2df7b2";
public const string STRUCT = "#0fba3a";
public const string INTERFACE = "#9b9b82";
public const string FIELD_STATIC = "#8d8dc6";
public const string FIELD_INSTANCE = "#c266ff";
public const string METHOD_STATIC = "#b55b02";
public const string METHOD_INSTANCE = "#ff8000";
public const string PROP_STATIC = "#588075";
public const string PROP_INSTANCE = "#55a38e";
public const string LOCAL_ARG = "#a6e9e9";
internal const string ARRAY_TOKEN = "[]";
internal const string OPEN_COLOR = "<color=";
internal const string CLOSE_COLOR = "</color>";
internal const string OPEN_ITALIC = "<i>";
internal const string CLOSE_ITALIC = "</i>";
public static readonly Color StringOrange = new Color(0.83f, 0.61f, 0.52f);
public static readonly Color EnumGreen = new Color(0.57f, 0.76f, 0.43f);
public static readonly Color KeywordBlue = new Color(0.3f, 0.61f, 0.83f);
public static readonly string keywordBlueHex = KeywordBlue.ToHex();
public static readonly Color NumberGreen = new Color(0.71f, 0.8f, 0.65f);
internal static string GetClassColor(Type type)
{
if (type.IsAbstract && type.IsSealed)
return CLASS_STATIC;
else if (type.IsEnum || type.IsGenericParameter)
return CONST;
else if (type.IsValueType)
return STRUCT;
else if (type.IsInterface)
return INTERFACE;
else
return CLASS_INSTANCE;
}
//private static readonly StringBuilder syntaxBuilder = new StringBuilder(2156);
private static bool GetNamespace(Type type, out string ns)
{
var ret = !string.IsNullOrEmpty(ns = type.Namespace?.Trim());
return ret;
}
public static string Parse(Type type, bool includeNamespace, MemberInfo memberInfo = null)
{
if (type == null)
throw new ArgumentNullException("type");
var syntaxBuilder = new StringBuilder();
// Namespace
bool isGeneric = type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter);
if (!isGeneric)
{
if (includeNamespace && GetNamespace(type, out string ns))
syntaxBuilder.Append(OPEN_COLOR).Append(NAMESPACE).Append('>').Append(ns).Append(CLOSE_COLOR).Append('.');
// Declaring type
var declaring = type.DeclaringType;
while (declaring != null)
{
syntaxBuilder.Append(HighlightType(declaring));
syntaxBuilder.Append('.');
declaring = declaring.DeclaringType;
}
}
// Highlight the type name
syntaxBuilder.Append(HighlightType(type));
// If memberInfo, highlight the member info
if (memberInfo != null)
{
syntaxBuilder.Append('.');
int start = syntaxBuilder.Length - 1;
syntaxBuilder.Append(OPEN_COLOR)
.Append(GetMemberInfoColor(memberInfo, out bool isStatic))
.Append('>')
.Append(memberInfo.Name)
.Append(CLOSE_COLOR);
if (isStatic)
{
syntaxBuilder.Insert(start, OPEN_ITALIC);
syntaxBuilder.Append(CLOSE_ITALIC);
}
if (memberInfo is MethodInfo method)
{
var args = method.GetGenericArguments();
if (args.Length > 0)
syntaxBuilder.Append('<').Append(ParseGenericArgs(args, true)).Append('>');
}
}
return syntaxBuilder.ToString();
}
private static readonly Dictionary<string, string> typeToRichType = new Dictionary<string, string>();
private static bool EndsWith(this StringBuilder sb, string _string)
{
int len = _string.Length;
if (sb.Length < len)
return false;
int stringpos = 0;
for (int i = sb.Length - len; i < sb.Length; i++, stringpos++)
{
if (sb[i] != _string[stringpos])
return false;
}
return true;
}
private static string HighlightType(Type type)
{
string key = type.ToString();
if (typeToRichType.ContainsKey(key))
return typeToRichType[key];
var sb = new StringBuilder(type.Name);
bool isArray = false;
if (sb.EndsWith(ARRAY_TOKEN))
{
isArray = true;
sb.Remove(sb.Length - 2, 2);
type = type.GetElementType();
}
if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter))
{
sb.Insert(0, $"<color={CONST}>");
sb.Append(CLOSE_COLOR);
}
else
{
var args = type.GetGenericArguments();
if (args.Length > 0)
{
// remove the `N from the end of the type name
// this could actually be >9 in some cases, so get the length of the length string and use that.
// eg, if it was "List`15", we would remove the ending 3 chars
int suffixLen = 1 + args.Length.ToString().Length;
// make sure the typename actually has expected "`N" format.
if (sb[sb.Length - suffixLen] == '`')
sb.Remove(sb.Length - suffixLen, suffixLen);
}
// highlight the base name itself
// do this after removing the `N suffix, so only the name itself is in the color tags.
sb.Insert(0, $"{OPEN_COLOR}{GetClassColor(type)}>");
sb.Append(CLOSE_COLOR);
// parse the generic args, if any
if (args.Length > 0)
{
sb.Append('<').Append(ParseGenericArgs(args)).Append('>');
}
}
if (isArray)
sb.Append('[').Append(']');
var ret = sb.ToString();
typeToRichType.Add(key, ret);
return ret;
}
public static string ParseGenericArgs(Type[] args, bool isGenericParams = false)
{
if (args.Length < 1)
return string.Empty;
var sb = new StringBuilder();
for (int i = 0; i < args.Length; i++)
{
if (i > 0)
sb.Append(',').Append(' ');
if (isGenericParams)
{
sb.Append(OPEN_COLOR).Append(CONST).Append('>').Append(args[i].Name).Append(CLOSE_COLOR);
continue;
}
sb.Append(HighlightType(args[i]));
}
return sb.ToString();
}
public static string GetMemberInfoColor(MemberTypes type)
{
switch (type)
{
case MemberTypes.Method: return METHOD_INSTANCE;
case MemberTypes.Property: return PROP_INSTANCE;
case MemberTypes.Field: return FIELD_INSTANCE;
default: return null;
}
}
public static string GetMemberInfoColor(MemberInfo memberInfo, out bool isStatic)
{
isStatic = false;
if (memberInfo is FieldInfo fi)
{
if (fi.IsStatic)
{
isStatic = true;
return FIELD_STATIC;
}
return FIELD_INSTANCE;
}
else if (memberInfo is MethodInfo mi)
{
if (mi.IsStatic)
{
isStatic = true;
return METHOD_STATIC;
}
return METHOD_INSTANCE;
}
else if (memberInfo is PropertyInfo pi)
{
if (pi.GetAccessors(true)[0].IsStatic)
{
isStatic = true;
return PROP_STATIC;
}
return PROP_INSTANCE;
}
//else if (memberInfo is EventInfo ei)
//{
// if (ei.GetAddMethod().IsStatic)
// {
// isStatic = true;
// return EVENT_STATIC;
// }
// return EVENT_INSTANCE;
//}
throw new NotImplementedException(memberInfo.GetType().Name + " is not supported");
}
}
}

View File

@ -1,184 +0,0 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityExplorer.Core.Runtime;
namespace UnityExplorer
{
public static class ToStringUtility
{
internal static Dictionary<string, MethodInfo> toStringMethods = new Dictionary<string, MethodInfo>();
internal static Dictionary<string, MethodInfo> toStringFormattedMethods = new Dictionary<string, MethodInfo>();
private const string nullString = "<color=grey>null</color>";
private const string nullUnknown = nullString + " (?)";
private const string destroyedString = "<color=red>Destroyed</color>";
private const string untitledString = "<i><color=grey>untitled</color></i>";
private const string eventSystemNamespace = "UnityEngine.EventSystem";
public static string PruneString(string s, int chars = 200, int lines = 5)
{
if (string.IsNullOrEmpty(s))
return s;
var sb = new StringBuilder(Math.Max(chars, s.Length));
int newlines = 0;
for (int i = 0; i < s.Length; i++)
{
if (newlines >= lines || i >= chars)
{
sb.Append("...");
break;
}
char c = s[i];
if (c == '\r' || c == '\n')
newlines++;
sb.Append(c);
}
return sb.ToString();
}
public static string ToStringWithType(object value, Type fallbackType, bool includeNamespace = true)
{
if (value.IsNullOrDestroyed() && fallbackType == null)
return nullUnknown;
Type type = value?.GetActualType() ?? fallbackType;
string richType = SignatureHighlighter.Parse(type, includeNamespace);
var sb = new StringBuilder();
if (value.IsNullOrDestroyed())
{
if (value == null)
{
sb.Append(nullString);
AppendRichType(sb, richType);
return sb.ToString();
}
else // destroyed unity object
{
sb.Append(destroyedString);
AppendRichType(sb, richType);
return sb.ToString();
}
}
if (value is UnityEngine.Object obj)
{
if (string.IsNullOrEmpty(obj.name))
sb.Append(untitledString);
else
{
sb.Append('"');
sb.Append(PruneString(obj.name, 50, 1));
sb.Append('"');
}
AppendRichType(sb, richType);
}
else if (type.FullName.StartsWith(eventSystemNamespace))
{
// UnityEngine.EventSystem classes can have some obnoxious ToString results with rich text.
sb.Append(richType);
}
else
{
var toString = ToString(value);
if (type.IsGenericType
|| toString == type.FullName
|| toString == $"{type.FullName} {type.FullName}"
|| toString == $"Il2Cpp{type.FullName}" || type.FullName == $"Il2Cpp{toString}")
{
sb.Append(richType);
}
else // the ToString contains some actual implementation, use that value.
{
sb.Append(PruneString(toString, 200, 5));
AppendRichType(sb, richType);
}
}
return sb.ToString();
}
private static void AppendRichType(StringBuilder sb, string richType)
{
sb.Append(' ');
sb.Append('(');
sb.Append(richType);
sb.Append(')');
}
private static string ToString(object value)
{
if (value.IsNullOrDestroyed())
{
if (value == null)
return nullString;
else // destroyed unity object
return destroyedString;
}
var type = value.GetActualType();
// Find and cache the relevant 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
value = value.TryCast(type);
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)
{
toString = ex.ReflectionExToString();
}
toString = ReflectionUtility.ProcessTypeInString(type, toString);
#if CPP
if (value is Il2CppSystem.Type cppType)
{
var monoType = Il2CppReflection.GetUnhollowedType(cppType);
if (monoType != null)
toString = ReflectionUtility.ProcessTypeInString(monoType, toString);
}
#endif
return toString;
}
}
}

View File

@ -1,111 +0,0 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using Object = UnityEngine.Object;
// Project-wide namespace for accessibility
namespace UnityExplorer
{
public static class UnityHelpers
{
// Time helpers, can't use Time.time since timeScale will affect it.
// default 10ms (one frame at 100fps)
public static bool OccuredEarlierThanDefault(this float time)
{
return Time.realtimeSinceStartup - 0.01f >= time;
}
public static bool OccuredEarlierThan(this float time, float secondsAgo)
{
return Time.realtimeSinceStartup - secondsAgo >= time;
}
/// <summary>
/// Check if an object is null, and if it's a UnityEngine.Object then also check if it was destroyed.
/// </summary>
public static bool IsNullOrDestroyed(this object obj, bool suppressWarning = true)
{
var unityObj = obj as Object;
if (obj == null)
{
if (!suppressWarning)
ExplorerCore.LogWarning("The target instance is null!");
return true;
}
else if (obj is Object)
{
if (!unityObj)
{
if (!suppressWarning)
ExplorerCore.LogWarning("The target UnityEngine.Object was destroyed!");
return true;
}
}
return false;
}
/// <summary>
/// Get the full Transform heirarchy path for this provided Transform.
/// </summary>
public static string GetTransformPath(this Transform transform, bool includeSelf = false)
{
var sb = new StringBuilder();
if (includeSelf)
sb.Append(transform.name);
while (transform.parent)
{
transform = transform.parent;
sb.Insert(0, '/');
sb.Insert(0, transform.name);
}
return sb.ToString();
}
/// <summary>
/// Converts Color to 6-digit RGB hex code (without # symbol). Eg, RGBA(1,0,0,1) -> FF0000
/// </summary>
public static string ToHex(this Color color)
{
byte r = (byte)Mathf.Clamp(Mathf.RoundToInt(color.r * 255f), 0, 255);
byte g = (byte)Mathf.Clamp(Mathf.RoundToInt(color.g * 255f), 0, 255);
byte b = (byte)Mathf.Clamp(Mathf.RoundToInt(color.b * 255f), 0, 255);
return $"{r:X2}{g:X2}{b:X2}";
}
/// <summary>
/// Assumes the string is a 6-digit RGB Hex color code (with optional leading #) which it will parse into a UnityEngine.Color.
/// Eg, FF0000 -> RGBA(1,0,0,1)
/// </summary>
public static Color ToColor(this string _string)
{
_string = _string.Replace("#", "");
if (_string.Length != 6)
return Color.magenta;
var r = byte.Parse(_string.Substring(0, 2), NumberStyles.HexNumber);
var g = byte.Parse(_string.Substring(2, 2), NumberStyles.HexNumber);
var b = byte.Parse(_string.Substring(4, 2), NumberStyles.HexNumber);
var color = new Color
{
r = (float)(r / (decimal)255),
g = (float)(g / (decimal)255),
b = (float)(b / (decimal)255),
a = 1
};
return color;
}
}
}

View File

@ -32,31 +32,9 @@ namespace UnityExplorer
public ExplorerBehaviour(IntPtr ptr) : base(ptr) { }
#endif
internal void Awake()
{
#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>();
#else
Camera.onPostRender += OnPostRender;
#endif
}
internal void Update()
{
ExplorerCore.Update();
}
internal void FixedUpdate()
{
ExplorerCore.FixedUpdate();
}
internal static void OnPostRender(Camera camera)
{
ExplorerCore.OnPostRender();
}
}
}

View File

@ -1,30 +1,30 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core;
using UnityExplorer.Core.Config;
using UnityExplorer.Core.Input;
using UnityExplorer.Core.Runtime;
using UnityExplorer.Tests;
using UnityExplorer.Config;
using UnityExplorer.UI;
using UnityExplorer.UI.Inspectors;
using UnityExplorer.ObjectExplorer;
using UnityExplorer.UI.Panels;
using UnityExplorer.Runtime;
using UniverseLib.Input;
using UniverseLib.UI;
using System.Reflection;
namespace UnityExplorer
{
public static class ExplorerCore
{
public const string NAME = "UnityExplorer";
public const string VERSION = "4.0.2";
public const string VERSION = "4.5.6";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer";
public static IExplorerLoader Loader { get; private set; }
public static RuntimeContext Context { get; internal set; }
public static HarmonyLib.Harmony Harmony { get; } = new HarmonyLib.Harmony(GUID);
/// <summary>
/// Initialize UnityExplorer with the provided Loader implementation.
@ -40,46 +40,37 @@ namespace UnityExplorer
Log($"{NAME} {VERSION} initializing...");
ExplorerBehaviour.Setup();
if (!Directory.Exists(Loader.ExplorerFolder))
Directory.CreateDirectory(Loader.ExplorerFolder);
ConfigManager.Init(Loader.ConfigHandler);
UERuntimeHelper.Init();
ExplorerBehaviour.Setup();
ReflectionUtility.Init();
RuntimeProvider.Init();
SceneHandler.Init();
InputManager.Init();
RuntimeProvider.Instance.StartCoroutine(SetupCoroutine());
Log($"Finished core setup, waiting for UI setup...");
UniverseLib.Universe.Init(ConfigManager.Startup_Delay_Time.Value, LateInit, Log, new()
{
Disable_EventSystem_Override = ConfigManager.Disable_EventSystem_Override.Value,
Force_Unlock_Mouse = ConfigManager.Force_Unlock_Mouse.Value,
Unhollowed_Modules_Folder = loader.UnhollowedModulesFolder
});
}
// Do a delayed setup so that objects aren't destroyed instantly.
// This can happen for a multitude of reasons.
// Default delay is 1 second which is usually enough.
private static IEnumerator SetupCoroutine()
private static void LateInit()
{
yield return null;
Log($"Setting up late core features...");
float start = Time.realtimeSinceStartup;
float delay = ConfigManager.Startup_Delay_Time.Value;
while (delay > 0)
{
float diff = Math.Max(Time.deltaTime, Time.realtimeSinceStartup - start);
delay -= diff;
yield return null;
}
SceneHandler.Init();
Log($"Creating UI...");
UIManager.InitUI();
Log($"{NAME} {VERSION} initialized.");
//InspectorManager.Inspect(typeof(Tests.TestClass));
}
/// <summary>
@ -87,30 +78,22 @@ namespace UnityExplorer
/// </summary>
public static void Update()
{
RuntimeProvider.Instance.Update();
UIManager.Update();
// check master toggle
if (InputManager.GetKeyDown(ConfigManager.Master_Toggle.Value))
UIManager.ShowMenu = !UIManager.ShowMenu;
}
public static void FixedUpdate()
{
RuntimeProvider.Instance.ProcessFixedUpdate();
}
#region LOGGING
public static void OnPostRender()
{
RuntimeProvider.Instance.ProcessOnPostRender();
}
#region LOGGING
public static void Log(object message)
public static void Log(object message)
=> Log(message, LogType.Log);
public static void LogWarning(object message)
public static void LogWarning(object message)
=> Log(message, LogType.Warning);
public static void LogError(object message)
public static void LogError(object message)
=> Log(message, LogType.Error);
public static void LogUnity(object message, LogType logType)
@ -145,6 +128,6 @@ namespace UnityExplorer
}
}
#endregion
#endregion
}
}

67
src/Hooks/AddHookCell.cs Normal file
View File

@ -0,0 +1,67 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI;
using UnityExplorer.UI.Widgets;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets;
using UniverseLib.UI.Widgets.ScrollView;
namespace UnityExplorer.Hooks
{
public class AddHookCell : ICell
{
public bool Enabled => UIRoot.activeSelf;
public RectTransform Rect { get; set; }
public GameObject UIRoot { get; set; }
public float DefaultHeight => 30;
public Text MethodNameLabel;
public Text HookedLabel;
public ButtonRef HookButton;
public int CurrentDisplayedIndex;
private void OnHookClicked()
{
HookManager.Instance.AddHookClicked(CurrentDisplayedIndex);
}
public void Enable()
{
this.UIRoot.SetActive(true);
}
public void Disable()
{
this.UIRoot.SetActive(false);
}
public GameObject CreateContent(GameObject parent)
{
UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30));
Rect = UIRoot.GetComponent<RectTransform>();
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 5, childAlignment: TextAnchor.UpperLeft);
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
HookedLabel = UIFactory.CreateLabel(UIRoot, "HookedLabel", "✓", TextAnchor.MiddleCenter, Color.green);
UIFactory.SetLayoutElement(HookedLabel.gameObject, minHeight: 25, minWidth: 100);
HookButton = UIFactory.CreateButton(UIRoot, "HookButton", "Hook", new Color(0.2f, 0.25f, 0.2f));
UIFactory.SetLayoutElement(HookButton.Component.gameObject, minHeight: 25, minWidth: 100);
HookButton.OnClick += OnHookClicked;
MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
return UIRoot;
}
}
}

81
src/Hooks/HookCell.cs Normal file
View File

@ -0,0 +1,81 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets;
using UniverseLib.UI.Widgets.ScrollView;
namespace UnityExplorer.Hooks
{
public class HookCell : ICell
{
public bool Enabled => UIRoot.activeSelf;
public RectTransform Rect { get; set; }
public GameObject UIRoot { get; set; }
public float DefaultHeight => 30;
public Text MethodNameLabel;
public ButtonRef EditPatchButton;
public ButtonRef ToggleActiveButton;
public ButtonRef DeleteButton;
public int CurrentDisplayedIndex;
private void OnToggleActiveClicked()
{
HookManager.Instance.EnableOrDisableHookClicked(CurrentDisplayedIndex);
}
private void OnDeleteClicked()
{
HookManager.Instance.DeleteHookClicked(CurrentDisplayedIndex);
}
private void OnEditPatchClicked()
{
HookManager.Instance.EditPatchClicked(CurrentDisplayedIndex);
}
public GameObject CreateContent(GameObject parent)
{
UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30));
Rect = UIRoot.GetComponent<RectTransform>();
UIFactory.SetLayoutGroup<HorizontalLayoutGroup>(UIRoot, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft);
UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600);
UIRoot.AddComponent<ContentSizeFitter>().verticalFit = ContentSizeFitter.FitMode.PreferredSize;
MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft);
UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999);
ToggleActiveButton = UIFactory.CreateButton(UIRoot, "ToggleActiveBtn", "Enabled", new Color(0.15f, 0.2f, 0.15f));
UIFactory.SetLayoutElement(ToggleActiveButton.Component.gameObject, minHeight: 25, minWidth: 100);
ToggleActiveButton.OnClick += OnToggleActiveClicked;
DeleteButton = UIFactory.CreateButton(UIRoot, "DeleteButton", "Delete", new Color(0.2f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 100);
DeleteButton.OnClick += OnDeleteClicked;
EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Edit Hook Source", new Color(0.15f, 0.15f, 0.15f));
UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 150);
EditPatchButton.OnClick += OnEditPatchClicked;
return UIRoot;
}
public void Disable()
{
UIRoot.SetActive(false);
}
public void Enable()
{
UIRoot.SetActive(true);
}
}
}

236
src/Hooks/HookInstance.cs Normal file
View File

@ -0,0 +1,236 @@
using System;
using System.CodeDom.Compiler;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using HarmonyLib;
using Mono.CSharp;
using UnityExplorer.CSConsole;
using UniverseLib;
namespace UnityExplorer.Hooks
{
public class HookInstance
{
// Static
private static readonly StringBuilder evalOutput = new StringBuilder();
private static readonly ScriptEvaluator scriptEvaluator = new ScriptEvaluator(new StringWriter(evalOutput));
static HookInstance()
{
scriptEvaluator.Run("using System;");
scriptEvaluator.Run("using System.Reflection;");
scriptEvaluator.Run("using System.Collections;");
scriptEvaluator.Run("using System.Collections.Generic;");
}
// Instance
public bool Enabled;
public MethodInfo TargetMethod;
public string PatchSourceCode;
private readonly string shortSignature;
private PatchProcessor patchProcessor;
private MethodInfo postfix;
private MethodInfo prefix;
private MethodInfo finalizer;
private MethodInfo transpiler;
public HookInstance(MethodInfo targetMethod)
{
this.TargetMethod = targetMethod;
this.shortSignature = $"{targetMethod.DeclaringType.Name}.{targetMethod.Name}";
GenerateDefaultPatchSourceCode(targetMethod);
if (CompileAndGenerateProcessor(PatchSourceCode))
Patch();
}
// Evaluator.source_file
private static readonly FieldInfo fi_sourceFile = AccessTools.Field(typeof(Evaluator), "source_file");
// TypeDefinition.Definition
private static readonly PropertyInfo pi_Definition = AccessTools.Property(typeof(TypeDefinition), "Definition");
public bool CompileAndGenerateProcessor(string patchSource)
{
Unpatch();
try
{
patchProcessor = ExplorerCore.Harmony.CreateProcessor(TargetMethod);
// Dynamically compile the patch method
var codeBuilder = new StringBuilder();
codeBuilder.AppendLine($"public class DynamicPatch_{DateTime.Now.Ticks}");
codeBuilder.AppendLine("{");
codeBuilder.AppendLine(patchSource);
codeBuilder.AppendLine("}");
scriptEvaluator.Run(codeBuilder.ToString());
if (ScriptEvaluator._reportPrinter.ErrorsCount > 0)
throw new FormatException($"Unable to compile the generated patch!");
// TODO: Publicize MCS to avoid this reflection
// Get the most recent Patch type in the source file
var typeContainer = ((CompilationSourceFile)fi_sourceFile.GetValue(scriptEvaluator))
.Containers
.Last(it => it.MemberName.Name.StartsWith("DynamicPatch_"));
// Get the TypeSpec from the TypeDefinition, then get its "MetaInfo" (System.Type)
var patchClass = ((TypeSpec)pi_Definition.GetValue((Class)typeContainer, null)).GetMetaInfo();
// Create the harmony patches as defined
postfix = patchClass.GetMethod("Postfix", ReflectionUtility.FLAGS);
if (postfix != null)
patchProcessor.AddPostfix(new HarmonyMethod(postfix));
prefix = patchClass.GetMethod("Prefix", ReflectionUtility.FLAGS);
if (prefix != null)
patchProcessor.AddPrefix(new HarmonyMethod(prefix));
finalizer = patchClass.GetMethod("Finalizer", ReflectionUtility.FLAGS);
if (finalizer != null)
patchProcessor.AddFinalizer(new HarmonyMethod(finalizer));
transpiler = patchClass.GetMethod("Transpiler", ReflectionUtility.FLAGS);
if (transpiler != null)
patchProcessor.AddTranspiler(new HarmonyMethod(transpiler));
return true;
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception creating patch processor for target method {TargetMethod.FullDescription()}!\r\n{ex}");
return false;
}
}
private string GenerateDefaultPatchSourceCode(MethodInfo targetMethod)
{
var codeBuilder = new StringBuilder();
// Arguments
codeBuilder.Append("public static void Postfix(System.Reflection.MethodBase __originalMethod");
if (!targetMethod.IsStatic)
codeBuilder.Append($", {targetMethod.DeclaringType.FullName} __instance");
if (targetMethod.ReturnType != typeof(void))
codeBuilder.Append($", {targetMethod.ReturnType.FullName} __result");
var parameters = targetMethod.GetParameters();
int paramIdx = 0;
foreach (var param in parameters)
{
codeBuilder.Append($", {param.ParameterType.FullDescription().Replace("&", "")} __{paramIdx}");
paramIdx++;
}
codeBuilder.Append(")\n");
// Patch body
codeBuilder.AppendLine("{");
codeBuilder.AppendLine(" try {");
// Log message
var logMessage = new StringBuilder();
logMessage.Append($"Patch called: {shortSignature}\\n");
if (!targetMethod.IsStatic)
logMessage.Append("__instance: {__instance.ToString()}\\n");
paramIdx = 0;
foreach (var param in parameters)
{
logMessage.Append($"Parameter {paramIdx} {param.Name}: ");
Type pType = param.ParameterType;
if (pType.IsByRef) pType = pType.GetElementType();
if (pType.IsValueType)
logMessage.Append($"{{__{paramIdx}.ToString()}}");
else
logMessage.Append($"{{__{paramIdx}?.ToString() ?? \"null\"}}");
logMessage.Append("\\n");
paramIdx++;
}
if (targetMethod.ReturnType != typeof(void))
{
logMessage.Append("Return value: ");
if (targetMethod.ReturnType.IsValueType)
logMessage.Append("{__result.ToString()}");
else
logMessage.Append("{__result?.ToString() ?? \"null\"}");
logMessage.Append("\\n");
}
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.Log($\"{logMessage}\");");
codeBuilder.AppendLine(" }");
codeBuilder.AppendLine(" catch (System.Exception ex) {");
codeBuilder.AppendLine($" UnityExplorer.ExplorerCore.LogWarning($\"Exception in patch of {shortSignature}:\\n{{ex}}\");");
codeBuilder.AppendLine(" }");
// End patch body
codeBuilder.AppendLine("}");
//ExplorerCore.Log(codeBuilder.ToString());
return PatchSourceCode = codeBuilder.ToString();
}
public void TogglePatch()
{
if (!Enabled)
Patch();
else
Unpatch();
}
public void Patch()
{
try
{
patchProcessor.Patch();
Enabled = true;
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception hooking method!\r\n{ex}");
}
}
public void Unpatch()
{
try
{
if (prefix != null)
patchProcessor.Unpatch(prefix);
if (postfix != null)
patchProcessor.Unpatch(postfix);
if (finalizer != null)
patchProcessor.Unpatch(finalizer);
if (transpiler != null)
patchProcessor.Unpatch(transpiler);
Enabled = false;
}
catch (Exception ex)
{
ExplorerCore.LogWarning($"Exception unpatching method: {ex}");
}
}
}
}

231
src/Hooks/HookManager.cs Normal file
View File

@ -0,0 +1,231 @@
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reflection;
using System.Text;
using HarmonyLib;
using UnityEngine;
using UnityExplorer.Runtime;
using UnityExplorer.CSConsole;
using UnityExplorer.UI;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Widgets;
using UniverseLib;
using UniverseLib.UI.Widgets;
using UniverseLib.UI.Widgets.ScrollView;
using UniverseLib.Utility;
namespace UnityExplorer.Hooks
{
public class HookManager : ICellPoolDataSource<HookCell>, ICellPoolDataSource<AddHookCell>
{
private static HookManager s_instance;
public static HookManager Instance => s_instance ?? (s_instance = new HookManager());
public HookManagerPanel Panel => UIManager.GetPanel<HookManagerPanel>(UIManager.Panels.HookManager);
// This class acts as the data source for both current hooks and eligable methods when adding hooks.
// 'isAddingMethods' keeps track of which pool is currently the displayed one, so our ItemCount reflects the
// correct pool cells.
private bool isAddingMethods;
public int ItemCount => isAddingMethods ? filteredEligableMethods.Count : currentHooks.Count;
// current hooks
private readonly HashSet<string> hookedSignatures = new HashSet<string>();
private readonly OrderedDictionary currentHooks = new OrderedDictionary();
// adding hooks
private readonly List<MethodInfo> currentAddEligableMethods = new List<MethodInfo>();
private readonly List<MethodInfo> filteredEligableMethods = new List<MethodInfo>();
// hook editor
private readonly LexerBuilder Lexer = new LexerBuilder();
private HookInstance currentEditedHook;
// ~~~~~~~~~~~ Main Current Hooks window ~~~~~~~~~~~
public void EnableOrDisableHookClicked(int index)
{
var hook = (HookInstance)currentHooks[index];
hook.TogglePatch();
Panel.HooksScrollPool.Refresh(true, false);
}
public void DeleteHookClicked(int index)
{
var hook = (HookInstance)currentHooks[index];
hook.Unpatch();
currentHooks.RemoveAt(index);
hookedSignatures.Remove(hook.TargetMethod.FullDescription());
Panel.HooksScrollPool.Refresh(true, false);
}
public void EditPatchClicked(int index)
{
Panel.SetPage(HookManagerPanel.Pages.HookSourceEditor);
var hook = (HookInstance)currentHooks[index];
currentEditedHook = hook;
Panel.EditorInput.Text = hook.PatchSourceCode;
}
// Set current hook cell
public void OnCellBorrowed(HookCell cell) { }
public void SetCell(HookCell cell, int index)
{
if (index >= this.currentHooks.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
var hook = (HookInstance)this.currentHooks[index];
cell.MethodNameLabel.text = SignatureHighlighter.HighlightMethod(hook.TargetMethod);
cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "Enabled" : "Disabled";
RuntimeHelper.SetColorBlockAuto(cell.ToggleActiveButton.Component,
hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f));
}
// ~~~~~~~~~~~ Add Hooks window ~~~~~~~~~~~
public void OnClassSelectedForHooks(string typeFullName)
{
var type = ReflectionUtility.GetTypeByName(typeFullName);
if (type == null)
{
ExplorerCore.LogWarning($"Could not find any type by name {typeFullName}!");
return;
}
Panel.SetAddHooksLabelType(SignatureHighlighter.Parse(type, true));
Panel.ResetMethodFilter();
filteredEligableMethods.Clear();
currentAddEligableMethods.Clear();
foreach (var method in type.GetMethods(ReflectionUtility.FLAGS))
{
if (method.IsGenericMethod || UERuntimeHelper.IsBlacklisted(method))
continue;
currentAddEligableMethods.Add(method);
filteredEligableMethods.Add(method);
}
isAddingMethods = true;
Panel.SetPage(HookManagerPanel.Pages.ClassMethodSelector);
Panel.AddHooksScrollPool.Refresh(true, true);
}
public void DoneAddingHooks()
{
isAddingMethods = false;
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
Panel.HooksScrollPool.Refresh(true, false);
}
public void AddHookClicked(int index)
{
if (index >= this.filteredEligableMethods.Count)
return;
AddHook(filteredEligableMethods[index]);
Panel.AddHooksScrollPool.Refresh(true, false);
}
public void AddHook(MethodInfo method)
{
var sig = method.FullDescription();
if (hookedSignatures.Contains(sig))
return;
var hook = new HookInstance(method);
if (hook.Enabled)
{
hookedSignatures.Add(sig);
currentHooks.Add(sig, hook);
}
}
public void OnAddHookFilterInputChanged(string input)
{
filteredEligableMethods.Clear();
if (string.IsNullOrEmpty(input))
filteredEligableMethods.AddRange(currentAddEligableMethods);
else
{
foreach (var method in currentAddEligableMethods)
{
if (method.Name.ContainsIgnoreCase(input))
filteredEligableMethods.Add(method);
}
}
Panel.AddHooksScrollPool.Refresh(true, true);
}
// Set eligable method cell
public void OnCellBorrowed(AddHookCell cell) { }
public void SetCell(AddHookCell cell, int index)
{
if (index >= this.filteredEligableMethods.Count)
{
cell.Disable();
return;
}
cell.CurrentDisplayedIndex = index;
var method = this.filteredEligableMethods[index];
cell.MethodNameLabel.text = SignatureHighlighter.HighlightMethod(method);
var sig = method.FullDescription();
if (hookedSignatures.Contains(sig))
{
cell.HookButton.Component.gameObject.SetActive(false);
cell.HookedLabel.gameObject.SetActive(true);
}
else
{
cell.HookButton.Component.gameObject.SetActive(true);
cell.HookedLabel.gameObject.SetActive(false);
}
}
// ~~~~~~~~~~~ Hook source editor window ~~~~~~~~~~~
public void OnEditorInputChanged(string value)
{
Panel.EditorHighlightText.text = Lexer.BuildHighlightedString(value, 0, value.Length - 1, 0,
Panel.EditorInput.Component.caretPosition, out _);
}
public void EditorInputCancel()
{
currentEditedHook = null;
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
}
public void EditorInputSave()
{
var input = Panel.EditorInput.Text;
bool wasEnabled = currentEditedHook.Enabled;
if (currentEditedHook.CompileAndGenerateProcessor(input))
{
if (wasEnabled)
currentEditedHook.Patch();
currentEditedHook.PatchSourceCode = input;
currentEditedHook = null;
Panel.SetPage(HookManagerPanel.Pages.CurrentHooks);
}
}
}
}

View File

@ -2,19 +2,23 @@
<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" />
<InputAssemblies Include="packages\Samboy063.Tomlet.3.1.3\lib\net35\Tomlet.dll" />
</ItemGroup>
<!-- Required references for ILRepack -->
<ItemGroup>
<ReferenceFolders Include="..\lib\" />
<ReferenceFolders Include="..\lib\HarmonyX\Harmony\bin\Release\net35\" />
<ReferenceFolders Include="packages\HarmonyX.2.5.2\lib\net35\" />
<ReferenceFolders Include="..\lib\BepInEx.6.IL2CPP\" />
<ReferenceFolders Include="..\lib\BepInEx.6.Mono\" />
<ReferenceFolders Include="..\lib\BepInEx.5\" />
<ReferenceFolders Include="..\lib\MelonLoader\" />
<ReferenceFolders Include="..\lib\Il2CppAssemblyUnhollower\UnhollowerBaseLib\bin\Release\net4.7.2\" />
</ItemGroup>
<ILRepack
Parallel="true"
Internalize="true"
@ -22,7 +26,8 @@
LibraryPath="@(ReferenceFolders)"
InputAssemblies="@(InputAssemblies)"
TargetKind="Dll"
OutputFile="$(OutputPath)$(AssemblyName).dll" />
OutputFile="$(OutputPath)$(AssemblyName).dll"
/>
</Target>
</Project>

View File

@ -5,14 +5,19 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Core.Input;
using UnityExplorer.UI.ObjectPool;
using UniverseLib.Input;
using UnityExplorer.UI;
using UniverseLib.UI.Models;
using UnityExplorer.UI.Panels;
using UnityExplorer.UI.Utility;
using UnityExplorer.UI.Widgets;
using UnityExplorer.UI.Widgets.AutoComplete;
using UniverseLib.UI.Widgets;
using UniverseLib.UI;
using UniverseLib;
using UniverseLib.UI.Widgets.ScrollView;
using UniverseLib.Utility;
namespace UnityExplorer.UI.Inspectors
namespace UnityExplorer.Inspectors
{
public class GameObjectInspector : InspectorBase
{
@ -41,7 +46,7 @@ namespace UnityExplorer.UI.Inspectors
GOControls.UpdateGameObjectInfo(true, true);
GOControls.UpdateTransformControlValues(true);
RuntimeProvider.Instance.StartCoroutine(InitCoroutine());
RuntimeHelper.StartCoroutine(InitCoroutine());
}
private IEnumerator InitCoroutine()
@ -64,7 +69,7 @@ namespace UnityExplorer.UI.Inspectors
addCompInput.Text = "";
TransformTree.Clear();
ComponentList.Clear();
UpdateComponents();
}
public override void CloseInspector()
@ -113,6 +118,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 +133,59 @@ namespace UnityExplorer.UI.Inspectors
private readonly List<bool> behaviourEnabledStates = new List<bool>();
// ComponentList.GetRootEntriesMethod
private List<Component> GetComponentEntries() => componentEntries;
private List<Component> GetComponentEntries() => GOTarget ? componentEntries : Enumerable.Empty<Component>().ToList();
public void UpdateComponents()
{
if (!GOTarget)
{
componentEntries.Clear();
compInstanceIDs.Clear();
behaviourEntries.Clear();
behaviourEnabledStates.Clear();
ComponentList.RefreshData();
ComponentList.ScrollPool.Refresh(true, true);
return;
}
// Check if we actually need to refresh the component cells or not.
var comps = GOTarget.GetComponents<Component>();
var behaviours = GOTarget.GetComponents<Behaviour>();
bool needRefresh = false;
if (comps.Length != componentEntries.Count || behaviours.Length != behaviourEntries.Count)
{
needRefresh = true;
}
else
{
foreach (var comp in comps)
{
if (!compInstanceIDs.Contains(comp.GetInstanceID()))
{
needRefresh = true;
break;
}
}
if (!needRefresh)
int count = 0;
foreach (var comp in comps)
{
if (!comp)
continue;
count++;
if (!compInstanceIDs.Contains(comp.GetInstanceID()))
{
for (int i = 0; i < behaviours.Length; i++)
needRefresh = true;
break;
}
}
if (!needRefresh)
{
if (count != componentEntries.Count)
needRefresh = true;
else
{
count = 0;
foreach (var behaviour in behaviours)
{
var behaviour = behaviours[i];
if (behaviour.enabled != behaviourEnabledStates[i])
if (!behaviour)
continue;
if (count >= behaviourEnabledStates.Count || behaviour.enabled != behaviourEnabledStates[count])
{
needRefresh = true;
break;
}
count++;
}
if (!needRefresh && count != behaviourEntries.Count)
needRefresh = true;
}
}
@ -168,9 +194,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 +205,7 @@ namespace UnityExplorer.UI.Inspectors
behaviourEnabledStates.Clear();
foreach (var behaviour in behaviours)
{
if (!behaviour) continue;
behaviourEntries.Add(behaviour);
behaviourEnabledStates.Add(behaviour.enabled);
}
@ -195,14 +222,14 @@ namespace UnityExplorer.UI.Inspectors
TransformTree.RefreshData(true, false);
}
private void OnAddComponentClicked(string input)
{
if (ReflectionUtility.AllTypes.TryGetValue(input, out Type type))
if (ReflectionUtility.GetTypeByName(input) is Type type)
{
try
{
RuntimeProvider.Instance.AddComponent<Component>(GOTarget, type);
RuntimeHelper.AddComponent<Component>(GOTarget, type);
UpdateComponents();
}
catch (Exception ex)
@ -216,15 +243,14 @@ namespace UnityExplorer.UI.Inspectors
}
}
#region UI Construction
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,
var scrollObj = UIFactory.CreateScrollView(UIRoot, "GameObjectInspector", out Content, out var scrollbar,
new Color(0.065f, 0.065f, 0.065f));
UIFactory.SetLayoutElement(scrollObj, minHeight: 250, preferredHeight: 300, flexibleHeight: 0, flexibleWidth: 9999);
@ -232,7 +258,7 @@ namespace UnityExplorer.UI.Inspectors
// Construct GO Controls
GOControls = new GameObjectControls(this);
ConstructLists();
return UIRoot;

View File

@ -4,9 +4,12 @@ using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.UI.Widgets;
using UniverseLib;
using UniverseLib.UI;
using UniverseLib.UI.Models;
using UniverseLib.UI.Widgets.ButtonList;
namespace UnityExplorer.UI.Inspectors
namespace UnityExplorer.Inspectors
{
public class ComponentCell : ButtonCell
{
@ -31,7 +34,8 @@ namespace UnityExplorer.UI.Inspectors
var root = base.CreateContent(parent);
// Add mask to button so text doesnt overlap on Close button
this.Button.Component.gameObject.AddComponent<Mask>().showMaskGraphic = true;
//this.Button.Component.gameObject.AddComponent<Mask>().showMaskGraphic = true;
this.Button.ButtonText.horizontalOverflow = HorizontalWrapMode.Wrap;
// Behaviour toggle

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