├── .gitattributes ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.yaml │ ├── config.yml │ ├── enhancement.yaml │ └── other.yaml └── workflows │ └── dotnet.yml ├── .gitignore ├── LICENSE ├── README.md ├── THIRDPARTY_LICENSES.md ├── UnityEditorPackage ├── LICENSE.md ├── README.md ├── Runtime │ ├── 0Harmony.dll │ ├── Mono.Cecil.Mdb.dll │ ├── Mono.Cecil.Pdb.dll │ ├── Mono.Cecil.Rocks.dll │ ├── Mono.Cecil.dll │ ├── MonoMod.RuntimeDetour.dll │ ├── MonoMod.Utils.dll │ ├── Tomlet.dll │ ├── UnityExplorer.STANDALONE.Mono.dll │ ├── UnityExplorer.prefab │ ├── UniverseLib.Mono.dll │ └── mcs.dll ├── Third Party Notices.md └── package.json ├── build.ps1 ├── img ├── icon.png └── preview.png ├── lib ├── ILRepack.exe ├── interop │ ├── Il2CppSystem.Core.dll │ ├── Il2Cppmscorlib.dll │ ├── UnityEngine.AssetBundleModule.dll │ ├── UnityEngine.AudioModule.dll │ ├── UnityEngine.CoreModule.dll │ ├── UnityEngine.IMGUIModule.dll │ ├── UnityEngine.PhysicsModule.dll │ ├── UnityEngine.TextRenderingModule.dll │ ├── UnityEngine.UI.dll │ ├── UnityEngine.UIModule.dll │ └── UnityEngine.dll ├── net35 │ ├── BepInEx.Core.dll │ ├── BepInEx.Unity.dll │ ├── BepInEx.dll │ ├── MelonLoader.dll │ ├── UnityEngine.UI.dll │ ├── UnityEngine.dll │ └── mcs.dll ├── net472 │ ├── BepInEx.Core.dll │ └── BepInEx.IL2CPP.dll ├── net6 │ ├── MelonLoader.dll │ ├── System.Runtime.dll │ └── mcs.dll └── unhollowed │ ├── Il2CppSystem.Core.dll │ ├── Il2Cppmscorlib.dll │ ├── UnityEngine.AssetBundleModule.dll │ ├── UnityEngine.AudioModule.dll │ ├── UnityEngine.CoreModule.dll │ ├── UnityEngine.IMGUIModule.dll │ ├── UnityEngine.PhysicsModule.dll │ ├── UnityEngine.TextRenderingModule.dll │ ├── UnityEngine.UI.dll │ ├── UnityEngine.UIModule.dll │ └── UnityEngine.dll └── src ├── CSConsole ├── CSAutoCompleter.cs ├── ConsoleController.cs ├── LexerBuilder.cs ├── Lexers │ ├── CommentLexer.cs │ ├── KeywordLexer.cs │ ├── Lexer.cs │ ├── NumberLexer.cs │ ├── StringLexer.cs │ └── SymbolLexer.cs ├── ScriptEvaluator.cs └── ScriptInteraction.cs ├── CacheObject ├── CacheConfigEntry.cs ├── CacheConstructor.cs ├── CacheField.cs ├── CacheKeyValuePair.cs ├── CacheListEntry.cs ├── CacheMember.cs ├── CacheMemberFactory.cs ├── CacheMethod.cs ├── CacheObjectBase.cs ├── CacheProperty.cs ├── ICacheObjectController.cs ├── IValues │ ├── InteractiveColor.cs │ ├── InteractiveDictionary.cs │ ├── InteractiveEnum.cs │ ├── InteractiveList.cs │ ├── InteractiveString.cs │ ├── InteractiveValue.cs │ └── InteractiveValueStruct.cs └── Views │ ├── CacheConfigCell.cs │ ├── CacheKeyValuePairCell.cs │ ├── CacheListEntryCell.cs │ ├── CacheMemberCell.cs │ └── CacheObjectCell.cs ├── Config ├── ConfigElement.cs ├── ConfigHandler.cs ├── ConfigManager.cs ├── IConfigElement.cs └── InternalConfigHandler.cs ├── ExplorerBehaviour.cs ├── ExplorerCore.cs ├── Hooks ├── AddHookCell.cs ├── HookCell.cs ├── HookCreator.cs ├── HookInstance.cs └── HookList.cs ├── Inspectors ├── GameObjectInspector.cs ├── InspectorBase.cs ├── InspectorManager.cs ├── InspectorTab.cs ├── MouseInspector.cs ├── MouseInspectors │ ├── MouseInspectorBase.cs │ ├── UiInspector.cs │ └── WorldInspector.cs └── ReflectionInspector.cs ├── Loader ├── BepInEx │ ├── BepInExConfigHandler.cs │ └── ExplorerBepInPlugin.cs ├── IExplorerLoader.cs ├── MelonLoader │ ├── ExplorerMelonMod.cs │ └── MelonLoaderConfigHandler.cs └── Standalone │ ├── Editor │ ├── ExplorerEditorBehaviour.cs │ └── ExplorerEditorLoader.cs │ ├── ExplorerStandalone.cs │ └── StandaloneConfigHandler.cs ├── ObjectExplorer ├── ObjectSearch.cs ├── SceneExplorer.cs ├── SceneHandler.cs └── SearchProvider.cs ├── Properties └── AssemblyInfo.cs ├── Runtime ├── Il2CppHelper.cs ├── MonoHelper.cs ├── UERuntimeHelper.cs └── UnityCrashPrevention.cs ├── Tests └── TestClass.cs ├── UI ├── DisplayManager.cs ├── ExplorerUIBase.cs ├── Notification.cs ├── Panels │ ├── AutoCompleteModal.cs │ ├── CSConsolePanel.cs │ ├── ClipboardPanel.cs │ ├── FreeCamPanel.cs │ ├── HookManagerPanel.cs │ ├── InspectorPanel.cs │ ├── LogPanel.cs │ ├── MouseInspectorResultsPanel.cs │ ├── ObjectExplorerPanel.cs │ ├── OptionsPanel.cs │ ├── UEPanel.cs │ └── UEPanelDragger.cs ├── UEPanelManager.cs ├── UIManager.cs └── Widgets │ ├── AutoComplete │ ├── EnumCompleter.cs │ ├── ISuggestionProvider.cs │ ├── Suggestion.cs │ └── TypeCompleter.cs │ ├── EvaluateWidget │ ├── BaseArgumentHandler.cs │ ├── EvaluateWidget.cs │ ├── GenericArgumentHandler.cs │ ├── GenericConstructorWidget.cs │ └── ParameterHandler.cs │ ├── GameObjects │ ├── AxisControl.cs │ ├── ComponentCell.cs │ ├── ComponentList.cs │ ├── GameObjectControls.cs │ ├── GameObjectInfoPanel.cs │ ├── TransformControls.cs │ ├── TransformType.cs │ └── Vector3Control.cs │ ├── TimeScaleWidget.cs │ └── UnityObjects │ ├── AudioClipWidget.cs │ ├── MaterialWidget.cs │ ├── Texture2DWidget.cs │ └── UnityObjectWidget.cs ├── UnityExplorer.csproj ├── UnityExplorer.sln └── nuget.config /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | ko_fi: sinaidev 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: File a bug or crash report 3 | title: "[Bug]: " 4 | labels: [bug] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for submitting a bug report, please fill out as much detail as possible. 10 | - type: checkboxes 11 | id: latestversion 12 | attributes: 13 | label: Are you on the latest version of UnityExplorer? 14 | description: If not, you must update first. 15 | options: 16 | - label: Yes, I'm on the latest version of UnityExplorer. 17 | required: true 18 | - type: dropdown 19 | id: version 20 | attributes: 21 | label: Which release are you using? 22 | description: Please select your environment for UnityExplorer. 23 | options: 24 | - BepInEx IL2CPP 25 | - BepInEx 6.X Mono 26 | - BepInEx 5.X Mono 27 | - MelonLoader IL2CPP 28 | - MelonLoader Mono 29 | - Standalone IL2CPP 30 | - Standalone Mono 31 | validations: 32 | required: true 33 | - type: textarea 34 | id: game 35 | attributes: 36 | label: Which game did this occur on? 37 | description: Please tell us the name of the game. If it's a personal or private project, just let us know the Unity version. 38 | validations: 39 | required: true 40 | - type: textarea 41 | id: what-happened 42 | attributes: 43 | label: Describe the issue. 44 | description: What happened? Should something else have happened instead? Please provide steps to reproduce the issue if possible. 45 | placeholder: Tell us what you see! 46 | validations: 47 | required: true 48 | - type: textarea 49 | id: logs 50 | attributes: 51 | label: Relevant log output 52 | description: | 53 | Please copy and paste any relevant logs and stack traces. 54 | * Unity log: `%userprofile%\AppData\LocalLow\{Company}\{Game}\Player.log` or `output_log.txt` 55 | * BepInEx: `BepInEx\LogOutput.log` 56 | * MelonLoader: `MelonLoader\latest.log` 57 | * Standalone: `{DLL_Location}\UnityExplorer\Logs\` (pick the most recent one) 58 | render: shell 59 | validations: 60 | required: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/enhancement.yaml: -------------------------------------------------------------------------------- 1 | name: New feature or enhancement 2 | description: Suggest or discuss a feature or enhancement for UnityExplorer 3 | title: "[Enhancement]: " 4 | labels: [enhancement] 5 | body: 6 | - type: markdown 7 | attributes: 8 | value: | 9 | Thanks for taking the time to discuss UnityExplorer, please provide as much detail as possible. 10 | - type: textarea 11 | id: description 12 | attributes: 13 | label: Describe the new feature or enhancement 14 | description: | 15 | Please go into as much detail as necessary in describing the new feature or enhancement. 16 | If providing examples or suggestions for the required C# code, please use syntax-highlighted code blocks. 17 | validations: 18 | required: true 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/other.yaml: -------------------------------------------------------------------------------- 1 | name: Other 2 | description: Something else? 3 | title: "[Other]: " 4 | labels: [Other] 5 | body: 6 | - type: textarea 7 | id: description 8 | attributes: 9 | label: Describe the issue 10 | description: | 11 | Please describe the issue in as much detail as possible. 12 | validations: 13 | required: true 14 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: Build UnityExplorer 2 | 3 | # Controls when the action will run. 4 | on: 5 | push: 6 | branches: [master] 7 | # Allows you to run this workflow manually from the Actions tab 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: windows-latest 13 | if: "!contains(github.event.head_commit.message, '-noci')" 14 | 15 | steps: 16 | # Setup 17 | 18 | - name: Checkout latest 19 | uses: actions/checkout@v2 20 | 21 | - name: Setup dotnet 22 | uses: actions/setup-dotnet@v2 23 | with: 24 | dotnet-version: '6.0.x' 25 | include-prerelease: true 26 | 27 | # Run build script 28 | - run: | 29 | ./build.ps1 30 | 31 | # Upload artifacts 32 | 33 | # BepInEx IL2CPP 34 | - uses: actions/upload-artifact@v2 35 | with: 36 | name: UnityExplorer.BepInEx.IL2CPP.zip 37 | path: ./Release/UnityExplorer.BepInEx.IL2CPP/ 38 | 39 | # BepInEx IL2CPP CoreCLR 40 | - uses: actions/upload-artifact@v2 41 | with: 42 | name: UnityExplorer.BepInEx.IL2CPP.CoreCLR.zip 43 | path: ./Release/UnityExplorer.BepInEx.IL2CPP.CoreCLR/ 44 | 45 | # BepInEx 5 Mono 46 | - uses: actions/upload-artifact@v2 47 | with: 48 | name: UnityExplorer.BepInEx5.Mono.zip 49 | path: ./Release/UnityExplorer.BepInEx5.Mono/ 50 | 51 | # BepInEx 6 Mono 52 | - uses: actions/upload-artifact@v2 53 | with: 54 | name: UnityExplorer.BepInEx6.Mono.zip 55 | path: ./Release/UnityExplorer.BepInEx6.Mono/ 56 | 57 | # Editor 58 | - uses: actions/upload-artifact@v2 59 | with: 60 | name: UnityExplorer.Editor.zip 61 | path: ./UnityEditorPackage/ 62 | 63 | # MelonLoader IL2CPP net6preview 64 | - uses: actions/upload-artifact@v2 65 | with: 66 | name: UnityExplorer.MelonLoader.IL2CPP.net6preview.zip 67 | path: ./Release/UnityExplorer.MelonLoader.IL2CPP.net6preview/ 68 | 69 | # MelonLoader IL2CPP net472 70 | - uses: actions/upload-artifact@v2 71 | with: 72 | name: UnityExplorer.MelonLoader.IL2CPP.zip 73 | path: ./Release/UnityExplorer.MelonLoader.IL2CPP/ 74 | 75 | # MelonLoader Mono 76 | - uses: actions/upload-artifact@v2 77 | with: 78 | name: UnityExplorer.MelonLoader.Mono.zip 79 | path: ./Release/UnityExplorer.MelonLoader.Mono/ 80 | 81 | # Standalone Il2Cpp 82 | - uses: actions/upload-artifact@v2 83 | with: 84 | name: UnityExplorer.Standalone.IL2CPP.zip 85 | path: ./Release/UnityExplorer.Standalone.IL2CPP/ 86 | 87 | # Standalone Mono 88 | - uses: actions/upload-artifact@v2 89 | with: 90 | name: UnityExplorer.Standalone.Mono.zip 91 | path: ./Release/UnityExplorer.Standalone.Mono/ 92 | 93 | -------------------------------------------------------------------------------- /UnityEditorPackage/README.md: -------------------------------------------------------------------------------- 1 | # UnityExplorer 2 | 3 | 🔍 An in-game UI for exploring, debugging and modifying Unity games. 4 | ✔️ Supports most Unity versions from 5.2 to 2021+ (IL2CPP and Mono). 5 | ✨ Powered by [UniverseLib](https://github.com/sinai-dev/UniverseLib) 6 | 7 | # Setup 8 | 9 | * Install this package, either by using the Package Manager and importing the `package.json` file, or by manually dragging this folder into your Assets folder. 10 | * Drag the `Runtime/UnityExplorer` prefab into your scene, or create a GameObject and add the `Explorer Editor Behaviour` script to it. 11 | 12 | # Features 13 | 14 | ### Object Explorer 15 | 16 | * Use the Scene Explorer tab to traverse the active scenes, as well as the DontDestroyOnLoad and HideAndDontSave objects. 17 | * 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. 18 | * You can use the Scene Loader to easily load any of the scenes in the build (may not work for Unity 5.X games) 19 | * Use the Object Search tab to search for Unity objects (including GameObjects, Components, etc), C# Singletons or Static Classes. 20 | * Use the UnityObject search to look for any objects which derive from `UnityEngine.Object`, with optional filters 21 | * 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. 22 | 23 | ### Inspector 24 | 25 | 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. 26 | 27 | * The GameObject Inspector (tab prefix `[G]`) is used to inspect a `GameObject`, and to see and manipulate its Transform and Components. 28 | * You can edit any of the input fields in the inspector (excluding readonly fields) and press Enter to apply your changes. You can also do this to the GameObject path as a way to change the GameObject's parent. Press the Escape key to cancel your edits. 29 | * note: When inspecting a GameObject with a Canvas, the transform controls may be overridden by the RectTransform anchors. 30 | * The Reflection Inspectors (tab prefix `[R]` and `[S]`) are used for everything else 31 | * Automatic updating is not enabled by default, and you must press Apply for any changes you make to take effect. 32 | * Press the `▼` button to expand certain values such as strings, enums, lists, dictionaries, some structs, etc 33 | * Use the filters at the top to quickly find the members you are looking for 34 | * 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 35 | 36 | ### C# Console 37 | 38 | * The C# Console uses the `Mono.CSharp.Evaluator` to define temporary classes or run immediate REPL code. 39 | * 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). 40 | * See the "Help" dropdown in the C# console menu for more detailed information. 41 | 42 | ### Hook Manager 43 | 44 | * The Hooks panel allows you to hook methods at the click of a button for debugging purposes. 45 | * Simply enter any class (generic types not yet supported) and hook the methods you want from the menu. 46 | * 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`). You can define multiple patches if you wish. 47 | 48 | ### Mouse-Inspect 49 | 50 | * The "Mouse Inspect" dropdown in the "Inspector" panel allows you to inspect objects under the mouse. 51 | * World: uses Physics.Raycast to look for Colliders 52 | * UI: uses GraphicRaycasters to find UI objects 53 | 54 | ### Clipboard 55 | 56 | * The "Clipboard" panel allows you to see your current paste value, or clear it (resets it to `null`) 57 | * Can copy the value from any member in a Reflection Inspector, Enumerable or Dictionary, and from the target of any Inspector tab 58 | * Can paste values onto any member in a Reflection Inspector 59 | * Non-parsable arguments in Method/Property Evaluators allow pasting values 60 | * The C# Console has helper methods `Copy(obj)` and `Paste()` for accessing the Clipboard 61 | 62 | ### Settings 63 | 64 | * You can change the settings via the "Options" tab of the menu, or directly from the config file. 65 | * /sinai-dev-UnityExplorer~/config.cfg 66 | 67 | # Acknowledgments 68 | 69 | * [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. 70 | * [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. 71 | 72 | ### Disclaimer 73 | 74 | UnityExplorer is in no way associated with Unity Technologies. "Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere. 75 | -------------------------------------------------------------------------------- /UnityEditorPackage/Runtime/0Harmony.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/UnityEditorPackage/Runtime/0Harmony.dll -------------------------------------------------------------------------------- /UnityEditorPackage/Runtime/Mono.Cecil.Mdb.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/UnityEditorPackage/Runtime/Mono.Cecil.Mdb.dll -------------------------------------------------------------------------------- /UnityEditorPackage/Runtime/Mono.Cecil.Pdb.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/UnityEditorPackage/Runtime/Mono.Cecil.Pdb.dll -------------------------------------------------------------------------------- /UnityEditorPackage/Runtime/Mono.Cecil.Rocks.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/UnityEditorPackage/Runtime/Mono.Cecil.Rocks.dll -------------------------------------------------------------------------------- /UnityEditorPackage/Runtime/Mono.Cecil.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/UnityEditorPackage/Runtime/Mono.Cecil.dll -------------------------------------------------------------------------------- /UnityEditorPackage/Runtime/MonoMod.RuntimeDetour.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/UnityEditorPackage/Runtime/MonoMod.RuntimeDetour.dll -------------------------------------------------------------------------------- /UnityEditorPackage/Runtime/MonoMod.Utils.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/UnityEditorPackage/Runtime/MonoMod.Utils.dll -------------------------------------------------------------------------------- /UnityEditorPackage/Runtime/Tomlet.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/UnityEditorPackage/Runtime/Tomlet.dll -------------------------------------------------------------------------------- /UnityEditorPackage/Runtime/UnityExplorer.STANDALONE.Mono.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/UnityEditorPackage/Runtime/UnityExplorer.STANDALONE.Mono.dll -------------------------------------------------------------------------------- /UnityEditorPackage/Runtime/UnityExplorer.prefab: -------------------------------------------------------------------------------- 1 | %YAML 1.1 2 | %TAG !u! tag:unity3d.com,2011: 3 | --- !u!1 &2342243352467007562 4 | GameObject: 5 | m_ObjectHideFlags: 0 6 | m_CorrespondingSourceObject: {fileID: 0} 7 | m_PrefabInstance: {fileID: 0} 8 | m_PrefabAsset: {fileID: 0} 9 | serializedVersion: 6 10 | m_Component: 11 | - component: {fileID: 2342243352467007560} 12 | - component: {fileID: 2342243352467007563} 13 | m_Layer: 0 14 | m_Name: UnityExplorer 15 | m_TagString: Untagged 16 | m_Icon: {fileID: 0} 17 | m_NavMeshLayer: 0 18 | m_StaticEditorFlags: 0 19 | m_IsActive: 1 20 | --- !u!4 &2342243352467007560 21 | Transform: 22 | m_ObjectHideFlags: 0 23 | m_CorrespondingSourceObject: {fileID: 0} 24 | m_PrefabInstance: {fileID: 0} 25 | m_PrefabAsset: {fileID: 0} 26 | m_GameObject: {fileID: 2342243352467007562} 27 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} 28 | m_LocalPosition: {x: 0, y: 0, z: 0} 29 | m_LocalScale: {x: 1, y: 1, z: 1} 30 | m_Children: [] 31 | m_Father: {fileID: 0} 32 | m_RootOrder: 0 33 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} 34 | --- !u!114 &2342243352467007563 35 | MonoBehaviour: 36 | m_ObjectHideFlags: 0 37 | m_CorrespondingSourceObject: {fileID: 0} 38 | m_PrefabInstance: {fileID: 0} 39 | m_PrefabAsset: {fileID: 0} 40 | m_GameObject: {fileID: 2342243352467007562} 41 | m_Enabled: 1 42 | m_EditorHideFlags: 0 43 | m_Script: {fileID: 255528132, guid: 7e940c577136f52428020c8e128f06f3, type: 3} 44 | m_Name: 45 | m_EditorClassIdentifier: 46 | -------------------------------------------------------------------------------- /UnityEditorPackage/Runtime/UniverseLib.Mono.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/UnityEditorPackage/Runtime/UniverseLib.Mono.dll -------------------------------------------------------------------------------- /UnityEditorPackage/Runtime/mcs.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/UnityEditorPackage/Runtime/mcs.dll -------------------------------------------------------------------------------- /UnityEditorPackage/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "com.sinai-dev.unityexplorer", 3 | "version": "4.7.12", 4 | "displayName": "UnityExplorer", 5 | "description": "An in-game UI for exploring, debugging and modifying Unity games.", 6 | "unity": "2017.1", 7 | "documentationUrl": "https://github.com/sinai-dev/UnityExplorer", 8 | "changelogUrl": "https://github.com/sinai-dev/UnityExplorer/releases", 9 | "licensesUrl": "https://github.com/sinai-dev/UnityExplorer/blob/master/LICENSE", 10 | "keywords": [ 11 | "inspector", 12 | "debug", 13 | "runtime" 14 | ], 15 | "author": { 16 | "name": "Sinai", 17 | "url": "https://github.com/sinai-dev" 18 | } 19 | } -------------------------------------------------------------------------------- /img/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/img/icon.png -------------------------------------------------------------------------------- /img/preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/img/preview.png -------------------------------------------------------------------------------- /lib/ILRepack.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/ILRepack.exe -------------------------------------------------------------------------------- /lib/interop/Il2CppSystem.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/interop/Il2CppSystem.Core.dll -------------------------------------------------------------------------------- /lib/interop/Il2Cppmscorlib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/interop/Il2Cppmscorlib.dll -------------------------------------------------------------------------------- /lib/interop/UnityEngine.AssetBundleModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/interop/UnityEngine.AssetBundleModule.dll -------------------------------------------------------------------------------- /lib/interop/UnityEngine.AudioModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/interop/UnityEngine.AudioModule.dll -------------------------------------------------------------------------------- /lib/interop/UnityEngine.CoreModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/interop/UnityEngine.CoreModule.dll -------------------------------------------------------------------------------- /lib/interop/UnityEngine.IMGUIModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/interop/UnityEngine.IMGUIModule.dll -------------------------------------------------------------------------------- /lib/interop/UnityEngine.PhysicsModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/interop/UnityEngine.PhysicsModule.dll -------------------------------------------------------------------------------- /lib/interop/UnityEngine.TextRenderingModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/interop/UnityEngine.TextRenderingModule.dll -------------------------------------------------------------------------------- /lib/interop/UnityEngine.UI.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/interop/UnityEngine.UI.dll -------------------------------------------------------------------------------- /lib/interop/UnityEngine.UIModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/interop/UnityEngine.UIModule.dll -------------------------------------------------------------------------------- /lib/interop/UnityEngine.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/interop/UnityEngine.dll -------------------------------------------------------------------------------- /lib/net35/BepInEx.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/net35/BepInEx.Core.dll -------------------------------------------------------------------------------- /lib/net35/BepInEx.Unity.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/net35/BepInEx.Unity.dll -------------------------------------------------------------------------------- /lib/net35/BepInEx.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/net35/BepInEx.dll -------------------------------------------------------------------------------- /lib/net35/MelonLoader.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/net35/MelonLoader.dll -------------------------------------------------------------------------------- /lib/net35/UnityEngine.UI.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/net35/UnityEngine.UI.dll -------------------------------------------------------------------------------- /lib/net35/UnityEngine.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/net35/UnityEngine.dll -------------------------------------------------------------------------------- /lib/net35/mcs.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/net35/mcs.dll -------------------------------------------------------------------------------- /lib/net472/BepInEx.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/net472/BepInEx.Core.dll -------------------------------------------------------------------------------- /lib/net472/BepInEx.IL2CPP.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/net472/BepInEx.IL2CPP.dll -------------------------------------------------------------------------------- /lib/net6/MelonLoader.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/net6/MelonLoader.dll -------------------------------------------------------------------------------- /lib/net6/System.Runtime.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/net6/System.Runtime.dll -------------------------------------------------------------------------------- /lib/net6/mcs.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/net6/mcs.dll -------------------------------------------------------------------------------- /lib/unhollowed/Il2CppSystem.Core.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/unhollowed/Il2CppSystem.Core.dll -------------------------------------------------------------------------------- /lib/unhollowed/Il2Cppmscorlib.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/unhollowed/Il2Cppmscorlib.dll -------------------------------------------------------------------------------- /lib/unhollowed/UnityEngine.AssetBundleModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/unhollowed/UnityEngine.AssetBundleModule.dll -------------------------------------------------------------------------------- /lib/unhollowed/UnityEngine.AudioModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/unhollowed/UnityEngine.AudioModule.dll -------------------------------------------------------------------------------- /lib/unhollowed/UnityEngine.CoreModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/unhollowed/UnityEngine.CoreModule.dll -------------------------------------------------------------------------------- /lib/unhollowed/UnityEngine.IMGUIModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/unhollowed/UnityEngine.IMGUIModule.dll -------------------------------------------------------------------------------- /lib/unhollowed/UnityEngine.PhysicsModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/unhollowed/UnityEngine.PhysicsModule.dll -------------------------------------------------------------------------------- /lib/unhollowed/UnityEngine.TextRenderingModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/unhollowed/UnityEngine.TextRenderingModule.dll -------------------------------------------------------------------------------- /lib/unhollowed/UnityEngine.UI.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/unhollowed/UnityEngine.UI.dll -------------------------------------------------------------------------------- /lib/unhollowed/UnityEngine.UIModule.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/unhollowed/UnityEngine.UIModule.dll -------------------------------------------------------------------------------- /lib/unhollowed/UnityEngine.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GrahamKracker/UnityExplorer/5b72210c3923a2776b14fc866c9cf46dc586a346/lib/unhollowed/UnityEngine.dll -------------------------------------------------------------------------------- /src/CSConsole/CSAutoCompleter.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | using UnityExplorer.CSConsole.Lexers; 3 | using UnityExplorer.UI.Panels; 4 | using UnityExplorer.UI.Widgets.AutoComplete; 5 | using UniverseLib.UI.Models; 6 | 7 | namespace UnityExplorer.CSConsole 8 | { 9 | public class CSAutoCompleter : ISuggestionProvider 10 | { 11 | public InputFieldRef InputField => ConsoleController.Input; 12 | 13 | public bool AnchorToCaretPosition => true; 14 | 15 | bool ISuggestionProvider.AllowNavigation => true; 16 | 17 | public void OnSuggestionClicked(Suggestion suggestion) 18 | { 19 | ConsoleController.InsertSuggestionAtCaret(suggestion.UnderlyingValue); 20 | AutoCompleteModal.Instance.ReleaseOwnership(this); 21 | } 22 | 23 | private readonly HashSet delimiters = new() 24 | { 25 | '{', 26 | '}', 27 | ',', 28 | ';', 29 | '<', 30 | '>', 31 | '(', 32 | ')', 33 | '[', 34 | ']', 35 | '=', 36 | '|', 37 | '&', 38 | '?' 39 | }; 40 | 41 | private readonly List suggestions = new(); 42 | 43 | public void CheckAutocompletes() 44 | { 45 | if (string.IsNullOrEmpty(InputField.Text)) 46 | { 47 | AutoCompleteModal.Instance.ReleaseOwnership(this); 48 | return; 49 | } 50 | 51 | suggestions.Clear(); 52 | 53 | int caret = Math.Max(0, Math.Min(InputField.Text.Length - 1, InputField.Component.caretPosition - 1)); 54 | int startIdx = caret; 55 | 56 | // If the character at the caret index is whitespace or delimiter, 57 | // or if the next character (if it exists) is not whitespace, 58 | // then we don't want to provide suggestions. 59 | if (char.IsWhiteSpace(InputField.Text[caret]) 60 | || delimiters.Contains(InputField.Text[caret]) 61 | || (InputField.Text.Length > caret + 1 && !char.IsWhiteSpace(InputField.Text[caret + 1]))) 62 | { 63 | AutoCompleteModal.Instance.ReleaseOwnership(this); 64 | return; 65 | } 66 | 67 | // get the current composition string (from caret back to last delimiter) 68 | while (startIdx > 0) 69 | { 70 | startIdx--; 71 | char c = InputField.Text[startIdx]; 72 | if (delimiters.Contains(c) || char.IsWhiteSpace(c)) 73 | { 74 | startIdx++; 75 | break; 76 | } 77 | } 78 | string input = InputField.Text.Substring(startIdx, caret - startIdx + 1); 79 | 80 | // Get MCS completions 81 | 82 | string[] evaluatorCompletions = ConsoleController.Evaluator.GetCompletions(input, out string prefix); 83 | 84 | if (evaluatorCompletions != null && evaluatorCompletions.Any()) 85 | { 86 | suggestions.AddRange(from completion in evaluatorCompletions 87 | select new Suggestion(GetHighlightString(prefix, completion), completion)); 88 | } 89 | 90 | // Get manual namespace completions 91 | 92 | foreach (string ns in ReflectionUtility.AllNamespaces) 93 | { 94 | if (ns.StartsWith(input)) 95 | { 96 | if (!namespaceHighlights.ContainsKey(ns)) 97 | namespaceHighlights.Add(ns, $"{ns}"); 98 | 99 | string completion = ns.Substring(input.Length, ns.Length - input.Length); 100 | suggestions.Add(new Suggestion(namespaceHighlights[ns], completion)); 101 | } 102 | } 103 | 104 | // Get manual keyword completions 105 | 106 | foreach (string kw in KeywordLexer.keywords) 107 | { 108 | if (kw.StartsWith(input))// && kw.Length > input.Length) 109 | { 110 | if (!keywordHighlights.ContainsKey(kw)) 111 | keywordHighlights.Add(kw, $"{kw}"); 112 | 113 | string completion = kw.Substring(input.Length, kw.Length - input.Length); 114 | suggestions.Add(new Suggestion(keywordHighlights[kw], completion)); 115 | } 116 | } 117 | 118 | if (suggestions.Any()) 119 | { 120 | AutoCompleteModal.TakeOwnership(this); 121 | AutoCompleteModal.Instance.SetSuggestions(suggestions); 122 | } 123 | else 124 | { 125 | AutoCompleteModal.Instance.ReleaseOwnership(this); 126 | } 127 | } 128 | 129 | 130 | private readonly Dictionary namespaceHighlights = new(); 131 | 132 | private readonly Dictionary keywordHighlights = new(); 133 | 134 | private readonly StringBuilder highlightBuilder = new(); 135 | private const string OPEN_HIGHLIGHT = ""; 136 | 137 | private string GetHighlightString(string prefix, string completion) 138 | { 139 | highlightBuilder.Clear(); 140 | highlightBuilder.Append(OPEN_HIGHLIGHT); 141 | highlightBuilder.Append(prefix); 142 | highlightBuilder.Append(SignatureHighlighter.CLOSE_COLOR); 143 | highlightBuilder.Append(completion); 144 | return highlightBuilder.ToString(); 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/CSConsole/Lexers/CommentLexer.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.CSConsole.Lexers 2 | { 3 | public class CommentLexer : Lexer 4 | { 5 | private enum CommentType 6 | { 7 | Line, 8 | Block 9 | } 10 | 11 | // forest green 12 | protected override Color HighlightColor => new(0.34f, 0.65f, 0.29f, 1.0f); 13 | 14 | public override bool TryMatchCurrent(LexerBuilder lexer) 15 | { 16 | if (lexer.Current == '/') 17 | { 18 | lexer.PeekNext(); 19 | if (lexer.Current == '/') 20 | { 21 | // line comment. read to end of line or file. 22 | do 23 | { 24 | lexer.Commit(); 25 | lexer.PeekNext(); 26 | } 27 | while (!lexer.EndOrNewLine); 28 | 29 | return true; 30 | } 31 | else if (lexer.Current == '*') 32 | { 33 | // block comment, read until end of file or closing '*/' 34 | lexer.PeekNext(); 35 | do 36 | { 37 | lexer.PeekNext(); 38 | lexer.Commit(); 39 | } 40 | while (!lexer.EndOfInput && !(lexer.Current == '/' && lexer.Previous == '*')); 41 | 42 | return true; 43 | } 44 | } 45 | 46 | return false; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/CSConsole/Lexers/KeywordLexer.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace UnityExplorer.CSConsole.Lexers 4 | { 5 | public class KeywordLexer : Lexer 6 | { 7 | // system blue 8 | protected override Color HighlightColor => new(0.33f, 0.61f, 0.83f, 1.0f); 9 | 10 | public static readonly HashSet keywords = new() 11 | { 12 | // reserved keywords 13 | "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char", "checked", "class", "const", "continue", 14 | "decimal", "default", "delegate", "do", "double", "else", "enum", "event", "explicit", "extern", "false", "finally", 15 | "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int", "interface", "internal", "is", "lock", 16 | "long", "namespace", "new", "null", "object", "operator", "out", "override", "params", "private", "protected", "public", 17 | "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof", "stackalloc", "static", "string", "struct", "switch", 18 | "this", "throw", "true", "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using", "virtual", "void", 19 | "volatile", "while", 20 | // contextual keywords 21 | "add", "and", "alias", "ascending", "async", "await", "by", "descending", "dynamic", "equals", "from", "get", 22 | "global", "group", "init", "into", "join", "let", "managed", "nameof", "not", "notnull", "on", 23 | "or", "orderby", "partial", "record", "remove", "select", "set", "unmanaged", "value", "var", "when", "where", 24 | "where", "with", "yield", "nint", "nuint" 25 | }; 26 | 27 | public override bool TryMatchCurrent(LexerBuilder lexer) 28 | { 29 | char prev = lexer.Previous; 30 | char first = lexer.Current; 31 | 32 | // check for keywords 33 | if (lexer.IsDelimiter(prev, true) && char.IsLetter(first)) 34 | { 35 | // can be a keyword... 36 | 37 | StringBuilder sb = new(); 38 | sb.Append(lexer.Current); 39 | while (!lexer.EndOfInput && char.IsLetter(lexer.PeekNext())) 40 | sb.Append(lexer.Current); 41 | 42 | // next must be whitespace or delimiter 43 | if (!lexer.EndOfInput && !(char.IsWhiteSpace(lexer.Current) || lexer.IsDelimiter(lexer.Current))) 44 | return false; 45 | 46 | if (keywords.Contains(sb.ToString())) 47 | { 48 | if (!lexer.EndOfInput) 49 | lexer.RollbackBy(1); 50 | lexer.Commit(); 51 | return true; 52 | } 53 | 54 | return false; 55 | 56 | } 57 | else 58 | return false; 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/CSConsole/Lexers/Lexer.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.CSConsole.Lexers 2 | { 3 | public abstract class Lexer 4 | { 5 | public virtual IEnumerable Delimiters => Enumerable.Empty(); 6 | 7 | protected abstract Color HighlightColor { get; } 8 | 9 | public string ColorTag => colorTag ?? (colorTag = ""); 10 | private string colorTag; 11 | 12 | public abstract bool TryMatchCurrent(LexerBuilder lexer); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/CSConsole/Lexers/NumberLexer.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.CSConsole.Lexers 2 | { 3 | public class NumberLexer : Lexer 4 | { 5 | // Maroon 6 | protected override Color HighlightColor => new(0.58f, 0.33f, 0.33f, 1.0f); 7 | 8 | private bool IsNumeric(char c) => char.IsNumber(c) || c == '.'; 9 | 10 | public override bool TryMatchCurrent(LexerBuilder lexer) 11 | { 12 | // previous character must be whitespace or delimiter 13 | if (!lexer.IsDelimiter(lexer.Previous, true)) 14 | return false; 15 | 16 | if (!IsNumeric(lexer.Current)) 17 | return false; 18 | 19 | while (!lexer.EndOfInput) 20 | { 21 | lexer.Commit(); 22 | if (!IsNumeric(lexer.PeekNext())) 23 | break; 24 | } 25 | 26 | return true; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/CSConsole/Lexers/StringLexer.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.CSConsole.Lexers 2 | { 3 | public class StringLexer : Lexer 4 | { 5 | public override IEnumerable Delimiters => new[] { '"', '\'', }; 6 | 7 | // orange 8 | protected override Color HighlightColor => new(0.79f, 0.52f, 0.32f, 1.0f); 9 | 10 | public override bool TryMatchCurrent(LexerBuilder lexer) 11 | { 12 | if (lexer.Current == '"') 13 | { 14 | if (lexer.Previous == '@') 15 | { 16 | // verbatim string, continue until un-escaped quote. 17 | while (!lexer.EndOfInput) 18 | { 19 | lexer.Commit(); 20 | if (lexer.PeekNext() == '"') 21 | { 22 | lexer.Commit(); 23 | // possibly the end, check for escaped quotes. 24 | // commit the character and flip the escape bool for each quote. 25 | bool escaped = false; 26 | while (lexer.PeekNext() == '"') 27 | { 28 | lexer.Commit(); 29 | escaped = !escaped; 30 | } 31 | // if the last quote wasnt escaped, that was the end of the string. 32 | if (!escaped) 33 | break; 34 | } 35 | } 36 | } 37 | else 38 | { 39 | // normal string 40 | // continue until a quote which is not escaped, or end of input 41 | 42 | while (!lexer.EndOfInput) 43 | { 44 | lexer.Commit(); 45 | lexer.PeekNext(); 46 | if ((lexer.Current == '"') && lexer.Previous != '\\') 47 | { 48 | lexer.Commit(); 49 | break; 50 | } 51 | } 52 | } 53 | 54 | return true; 55 | } 56 | else if (lexer.Current == '\'') 57 | { 58 | // char 59 | 60 | while (!lexer.EndOfInput) 61 | { 62 | lexer.Commit(); 63 | lexer.PeekNext(); 64 | if ((lexer.Current == '\'') && lexer.Previous != '\\') 65 | { 66 | lexer.Commit(); 67 | break; 68 | } 69 | } 70 | 71 | return true; 72 | } 73 | else 74 | return false; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/CSConsole/Lexers/SymbolLexer.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.CSConsole.Lexers 2 | { 3 | public class SymbolLexer : Lexer 4 | { 5 | // silver 6 | protected override Color HighlightColor => new(0.6f, 0.6f, 0.6f); 7 | 8 | // all symbols are delimiters 9 | public override IEnumerable Delimiters => symbols.Where(it => it != '.'); // '.' is not a delimiter, only a separator. 10 | 11 | public static bool IsSymbol(char c) => symbols.Contains(c); 12 | 13 | public static readonly HashSet symbols = new() 14 | { 15 | '[', '{', '(', // open 16 | ']', '}', ')', // close 17 | '.', ',', ';', ':', '?', '@', // special 18 | 19 | // operators 20 | '+', '-', '*', '/', '%', '&', '|', '^', '~', '=', '<', '>', '!', 21 | }; 22 | 23 | public override bool TryMatchCurrent(LexerBuilder lexer) 24 | { 25 | // previous character must be delimiter, whitespace, or alphanumeric. 26 | if (!lexer.IsDelimiter(lexer.Previous, true, true)) 27 | return false; 28 | 29 | if (IsSymbol(lexer.Current)) 30 | { 31 | lexer.Commit(); 32 | return true; 33 | } 34 | 35 | return false; 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/CSConsole/ScriptEvaluator.cs: -------------------------------------------------------------------------------- 1 | using Mono.CSharp; 2 | using UnityExplorer.Config; 3 | 4 | // Thanks to ManlyMarco for this 5 | 6 | namespace UnityExplorer.CSConsole 7 | { 8 | public class ScriptEvaluator : Evaluator, IDisposable 9 | { 10 | internal TextWriter _textWriter; 11 | internal static StreamReportPrinter _reportPrinter; 12 | 13 | private static readonly HashSet StdLib = new(StringComparer.InvariantCultureIgnoreCase) 14 | { 15 | "mscorlib", 16 | "System.Core", 17 | "System", 18 | "System.Xml" 19 | }; 20 | 21 | public ScriptEvaluator(TextWriter tw) : base(BuildContext(tw)) 22 | { 23 | _textWriter = tw; 24 | 25 | ImportAppdomainAssemblies(); 26 | AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad; 27 | } 28 | 29 | public void Dispose() 30 | { 31 | AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad; 32 | _textWriter.Dispose(); 33 | } 34 | 35 | private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args) 36 | { 37 | string name = args.LoadedAssembly.GetName().Name; 38 | 39 | if (StdLib.Contains(name)) 40 | return; 41 | 42 | Reference(args.LoadedAssembly); 43 | } 44 | 45 | private void Reference(Assembly asm) 46 | { 47 | string name = asm.GetName().Name; 48 | 49 | if (name == "completions") // ignore assemblies generated by mcs' autocomplete. 50 | return; 51 | 52 | foreach (string blacklisted in ConfigManager.CSConsole_Assembly_Blacklist.Value.Split(';')) 53 | { 54 | string bl = blacklisted; 55 | if (bl.EndsWith(".dll", StringComparison.OrdinalIgnoreCase)) 56 | bl = blacklisted.Substring(0, bl.Length - 4); 57 | if (string.Equals(bl, name, StringComparison.OrdinalIgnoreCase)) 58 | return; 59 | } 60 | 61 | ReferenceAssembly(asm); 62 | } 63 | 64 | private static CompilerContext BuildContext(TextWriter tw) 65 | { 66 | _reportPrinter = new StreamReportPrinter(tw); 67 | 68 | CompilerSettings settings = new() 69 | { 70 | Version = LanguageVersion.Experimental, 71 | GenerateDebugInfo = false, 72 | StdLib = true, 73 | Target = Target.Library, 74 | WarningLevel = 0, 75 | EnhancedWarnings = false 76 | }; 77 | 78 | return new CompilerContext(settings, _reportPrinter); 79 | } 80 | 81 | private void ImportAppdomainAssemblies() 82 | { 83 | foreach (Assembly assembly in AppDomain.CurrentDomain.GetAssemblies()) 84 | { 85 | string name = assembly.GetName().Name; 86 | if (StdLib.Contains(name)) 87 | continue; 88 | 89 | try 90 | { 91 | Reference(assembly); 92 | } 93 | catch // (Exception ex) 94 | { 95 | //ExplorerCore.LogWarning($"Excepting referencing '{name}': {ex.GetType()}.{ex.Message}"); 96 | } 97 | } 98 | } 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/CSConsole/ScriptInteraction.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using Mono.CSharp; 3 | using System.Collections; 4 | using System.Text; 5 | using UnityExplorer.UI.Panels; 6 | 7 | namespace UnityExplorer.CSConsole 8 | { 9 | public class ScriptInteraction : InteractiveBase 10 | { 11 | public static object CurrentTarget 12 | => InspectorManager.ActiveInspector?.Target; 13 | 14 | public static object[] AllTargets 15 | => InspectorManager.Inspectors.Select(it => it.Target).ToArray(); 16 | 17 | public static void Log(object message) 18 | => ExplorerCore.Log(message); 19 | 20 | public static void Inspect(object obj) 21 | => InspectorManager.Inspect(obj); 22 | 23 | public static void Inspect(Type type) 24 | => InspectorManager.Inspect(type); 25 | 26 | public static Coroutine Start(IEnumerator ienumerator) 27 | => RuntimeHelper.StartCoroutine(ienumerator); 28 | 29 | public static void Stop(Coroutine coro) 30 | => RuntimeHelper.StopCoroutine(coro); 31 | 32 | public static void Copy(object obj) 33 | => ClipboardPanel.Copy(obj); 34 | 35 | public static object Paste() 36 | => ClipboardPanel.Current; 37 | 38 | public static void GetUsing() 39 | => Log(Evaluator.GetUsing()); 40 | 41 | public static void GetVars() 42 | { 43 | string vars = Evaluator.GetVars()?.Trim(); 44 | if (string.IsNullOrEmpty(vars)) 45 | ExplorerCore.LogWarning("No variables seem to be defined!"); 46 | else 47 | Log(vars); 48 | } 49 | 50 | public static void GetClasses() 51 | { 52 | if (AccessTools.Field(typeof(Evaluator), "source_file") 53 | .GetValue(Evaluator) is CompilationSourceFile sourceFile 54 | && sourceFile.Containers.Any()) 55 | { 56 | StringBuilder sb = new(); 57 | sb.Append($"There are {sourceFile.Containers.Count} defined classes:"); 58 | foreach (TypeDefinition type in sourceFile.Containers.Where(it => it is TypeDefinition)) 59 | { 60 | sb.Append($"\n\n{type.MemberName.Name}:"); 61 | foreach (MemberCore member in type.Members) 62 | sb.Append($"\n\t- {member.AttributeTargets}: \"{member.MemberName.Name}\" ({member.ModFlags})"); 63 | } 64 | Log(sb.ToString()); 65 | } 66 | else 67 | ExplorerCore.LogWarning("No classes seem to be defined."); 68 | 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/CacheObject/CacheConfigEntry.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.CacheObject.Views; 2 | using UnityExplorer.Config; 3 | 4 | namespace UnityExplorer.CacheObject 5 | { 6 | public class CacheConfigEntry : CacheObjectBase 7 | { 8 | public CacheConfigEntry(IConfigElement configElement) 9 | { 10 | this.RefConfigElement = configElement; 11 | this.FallbackType = configElement.ElementType; 12 | 13 | this.NameLabelText = $"{configElement.Name}" + 14 | $"\r\n{configElement.Description}"; 15 | this.NameLabelTextRaw = string.Empty; 16 | 17 | configElement.OnValueChangedNotify += UpdateValueFromSource; 18 | } 19 | 20 | public IConfigElement RefConfigElement; 21 | 22 | public override bool ShouldAutoEvaluate => true; 23 | public override bool HasArguments => false; 24 | public override bool CanWrite => true; 25 | 26 | public void UpdateValueFromSource() 27 | { 28 | //if (RefConfigElement.BoxedValue.Equals(this.Value)) 29 | // return; 30 | 31 | SetValueFromSource(RefConfigElement.BoxedValue); 32 | 33 | if (this.CellView != null) 34 | this.SetDataToCell(CellView); 35 | } 36 | 37 | public override void TrySetUserValue(object value) 38 | { 39 | this.Value = value; 40 | RefConfigElement.BoxedValue = value; 41 | } 42 | 43 | protected override bool TryAutoEvaluateIfUnitialized(CacheObjectCell cell) => true; 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/CacheObject/CacheConstructor.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Inspectors; 2 | 3 | namespace UnityExplorer.CacheObject 4 | { 5 | public class CacheConstructor : CacheMember 6 | { 7 | public ConstructorInfo CtorInfo { get; } 8 | readonly Type typeForStructConstructor; 9 | 10 | public override Type DeclaringType => typeForStructConstructor ?? CtorInfo.DeclaringType; 11 | public override bool IsStatic => true; 12 | public override bool ShouldAutoEvaluate => false; 13 | public override bool CanWrite => false; 14 | 15 | public CacheConstructor(ConstructorInfo ci) 16 | { 17 | this.CtorInfo = ci; 18 | } 19 | 20 | public CacheConstructor(Type typeForStructConstructor) 21 | { 22 | this.typeForStructConstructor = typeForStructConstructor; 23 | } 24 | 25 | public override void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member) 26 | { 27 | Type ctorReturnType; 28 | // if is parameterless struct ctor 29 | if (typeForStructConstructor != null) 30 | { 31 | ctorReturnType = typeForStructConstructor; 32 | this.Owner = inspector; 33 | 34 | // eg. Vector3.Vector3() 35 | this.NameLabelText = SignatureHighlighter.Parse(typeForStructConstructor, false); 36 | NameLabelText += $".{NameLabelText}()"; 37 | 38 | this.NameForFiltering = SignatureHighlighter.RemoveHighlighting(NameLabelText); 39 | this.NameLabelTextRaw = NameForFiltering; 40 | return; 41 | } 42 | else 43 | { 44 | base.SetInspectorOwner(inspector, member); 45 | 46 | Arguments = CtorInfo.GetParameters(); 47 | ctorReturnType = CtorInfo.DeclaringType; 48 | } 49 | 50 | if (ctorReturnType.IsGenericTypeDefinition) 51 | GenericArguments = ctorReturnType.GetGenericArguments(); 52 | } 53 | 54 | protected override object TryEvaluate() 55 | { 56 | try 57 | { 58 | Type returnType = DeclaringType; 59 | 60 | if (returnType.IsGenericTypeDefinition) 61 | returnType = DeclaringType.MakeGenericType(Evaluator.TryParseGenericArguments()); 62 | 63 | object ret; 64 | if (HasArguments) 65 | ret = Activator.CreateInstance(returnType, Evaluator.TryParseArguments()); 66 | else 67 | ret = Activator.CreateInstance(returnType, ArgumentUtility.EmptyArgs); 68 | 69 | LastException = null; 70 | return ret; 71 | } 72 | catch (Exception ex) 73 | { 74 | LastException = ex; 75 | return null; 76 | } 77 | } 78 | 79 | protected override void TrySetValue(object value) => throw new NotImplementedException("You can't set a constructor"); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/CacheObject/CacheField.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Inspectors; 2 | 3 | namespace UnityExplorer.CacheObject 4 | { 5 | public class CacheField : CacheMember 6 | { 7 | public FieldInfo FieldInfo { get; internal set; } 8 | public override Type DeclaringType => FieldInfo.DeclaringType; 9 | public override bool IsStatic => FieldInfo.IsStatic; 10 | public override bool CanWrite => m_canWrite ?? (bool)(m_canWrite = !(FieldInfo.IsLiteral && !FieldInfo.IsInitOnly)); 11 | private bool? m_canWrite; 12 | 13 | public override bool ShouldAutoEvaluate => true; 14 | 15 | public CacheField(FieldInfo fi) 16 | { 17 | this.FieldInfo = fi; 18 | } 19 | 20 | public override void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member) 21 | { 22 | base.SetInspectorOwner(inspector, member); 23 | } 24 | 25 | protected override object TryEvaluate() 26 | { 27 | try 28 | { 29 | object ret = FieldInfo.GetValue(DeclaringInstance); 30 | LastException = null; 31 | return ret; 32 | } 33 | catch (Exception ex) 34 | { 35 | LastException = ex; 36 | return null; 37 | } 38 | } 39 | 40 | protected override void TrySetValue(object value) 41 | { 42 | try 43 | { 44 | FieldInfo.SetValue(DeclaringInstance, value); 45 | } 46 | catch (Exception ex) 47 | { 48 | ExplorerCore.LogWarning(ex); 49 | } 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/CacheObject/CacheKeyValuePair.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.CacheObject.IValues; 2 | using UnityExplorer.CacheObject.Views; 3 | 4 | namespace UnityExplorer.CacheObject 5 | { 6 | public class CacheKeyValuePair : CacheObjectBase 7 | { 8 | //public InteractiveList CurrentList { get; set; } 9 | 10 | public int DictIndex; 11 | public object DictKey; 12 | public object DisplayedKey; 13 | 14 | public bool KeyInputWanted; 15 | public bool InspectWanted; 16 | public string KeyLabelText; 17 | public string KeyInputText; 18 | public string KeyInputTypeText; 19 | 20 | public float DesiredKeyWidth; 21 | public float DesiredValueWidth; 22 | 23 | public override bool ShouldAutoEvaluate => true; 24 | public override bool HasArguments => false; 25 | public override bool CanWrite => Owner.CanWrite; 26 | 27 | public void SetDictOwner(InteractiveDictionary dict, int index) 28 | { 29 | this.Owner = dict; 30 | this.DictIndex = index; 31 | } 32 | 33 | public void SetKey(object key) 34 | { 35 | this.DictKey = key; 36 | this.DisplayedKey = key.TryCast(); 37 | 38 | Type type = DisplayedKey.GetType(); 39 | if (ParseUtility.CanParse(type)) 40 | { 41 | KeyInputWanted = true; 42 | KeyInputText = ParseUtility.ToStringForInput(DisplayedKey, type); 43 | KeyInputTypeText = SignatureHighlighter.Parse(type, false); 44 | } 45 | else 46 | { 47 | KeyInputWanted = false; 48 | InspectWanted = type != typeof(bool) && !type.IsEnum; 49 | KeyLabelText = ToStringUtility.ToStringWithType(DisplayedKey, type, true); 50 | } 51 | } 52 | 53 | public override void SetDataToCell(CacheObjectCell cell) 54 | { 55 | base.SetDataToCell(cell); 56 | 57 | CacheKeyValuePairCell kvpCell = cell as CacheKeyValuePairCell; 58 | 59 | kvpCell.NameLabel.text = $"{DictIndex}:"; 60 | kvpCell.HiddenNameLabel.Text = ""; 61 | kvpCell.Image.color = DictIndex % 2 == 0 ? CacheListEntryCell.EvenColor : CacheListEntryCell.OddColor; 62 | 63 | if (KeyInputWanted) 64 | { 65 | kvpCell.KeyInputField.UIRoot.SetActive(true); 66 | kvpCell.KeyInputTypeLabel.gameObject.SetActive(true); 67 | kvpCell.KeyLabel.gameObject.SetActive(false); 68 | kvpCell.KeyInspectButton.Component.gameObject.SetActive(false); 69 | 70 | kvpCell.KeyInputField.Text = KeyInputText; 71 | kvpCell.KeyInputTypeLabel.text = KeyInputTypeText; 72 | } 73 | else 74 | { 75 | kvpCell.KeyInputField.UIRoot.SetActive(false); 76 | kvpCell.KeyInputTypeLabel.gameObject.SetActive(false); 77 | kvpCell.KeyLabel.gameObject.SetActive(true); 78 | kvpCell.KeyInspectButton.Component.gameObject.SetActive(InspectWanted); 79 | 80 | kvpCell.KeyLabel.text = KeyLabelText; 81 | } 82 | } 83 | 84 | public override void TrySetUserValue(object value) 85 | { 86 | (Owner as InteractiveDictionary).TrySetValueToKey(DictKey, value, DictIndex); 87 | } 88 | 89 | 90 | protected override bool TryAutoEvaluateIfUnitialized(CacheObjectCell cell) => true; 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /src/CacheObject/CacheListEntry.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.CacheObject.IValues; 2 | using UnityExplorer.CacheObject.Views; 3 | 4 | namespace UnityExplorer.CacheObject 5 | { 6 | public class CacheListEntry : CacheObjectBase 7 | { 8 | public int ListIndex; 9 | 10 | public override bool ShouldAutoEvaluate => true; 11 | public override bool HasArguments => false; 12 | public override bool CanWrite => Owner?.CanWrite ?? false; 13 | 14 | public void SetListOwner(InteractiveList list, int listIndex) 15 | { 16 | this.Owner = list; 17 | this.ListIndex = listIndex; 18 | } 19 | 20 | public override void SetDataToCell(CacheObjectCell cell) 21 | { 22 | base.SetDataToCell(cell); 23 | 24 | CacheListEntryCell listCell = cell as CacheListEntryCell; 25 | 26 | listCell.NameLabel.text = $"{ListIndex}:"; 27 | listCell.HiddenNameLabel.Text = ""; 28 | listCell.Image.color = ListIndex % 2 == 0 ? CacheListEntryCell.EvenColor : CacheListEntryCell.OddColor; 29 | } 30 | 31 | public override void TrySetUserValue(object value) 32 | { 33 | (Owner as InteractiveList).TrySetValueToIndex(value, this.ListIndex); 34 | } 35 | 36 | protected override bool TryAutoEvaluateIfUnitialized(CacheObjectCell cell) => true; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/CacheObject/CacheMethod.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Inspectors; 2 | 3 | namespace UnityExplorer.CacheObject 4 | { 5 | public class CacheMethod : CacheMember 6 | { 7 | public MethodInfo MethodInfo { get; } 8 | public override Type DeclaringType => MethodInfo.DeclaringType; 9 | public override bool CanWrite => false; 10 | public override bool IsStatic => MethodInfo.IsStatic; 11 | 12 | public override bool ShouldAutoEvaluate => false; 13 | 14 | public CacheMethod(MethodInfo mi) 15 | { 16 | this.MethodInfo = mi; 17 | } 18 | 19 | public override void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member) 20 | { 21 | base.SetInspectorOwner(inspector, member); 22 | 23 | Arguments = MethodInfo.GetParameters(); 24 | if (MethodInfo.IsGenericMethod) 25 | GenericArguments = MethodInfo.GetGenericArguments(); 26 | } 27 | 28 | protected override object TryEvaluate() 29 | { 30 | try 31 | { 32 | MethodInfo methodInfo = MethodInfo; 33 | if (methodInfo.IsGenericMethod) 34 | methodInfo = MethodInfo.MakeGenericMethod(Evaluator.TryParseGenericArguments()); 35 | 36 | object ret; 37 | if (HasArguments) 38 | ret = methodInfo.Invoke(DeclaringInstance, Evaluator.TryParseArguments()); 39 | else 40 | ret = methodInfo.Invoke(DeclaringInstance, ArgumentUtility.EmptyArgs); 41 | LastException = null; 42 | return ret; 43 | } 44 | catch (Exception ex) 45 | { 46 | LastException = ex; 47 | return null; 48 | } 49 | } 50 | 51 | protected override void TrySetValue(object value) => throw new NotImplementedException("You can't set a method"); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/CacheObject/CacheProperty.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Inspectors; 2 | 3 | namespace UnityExplorer.CacheObject 4 | { 5 | public class CacheProperty : CacheMember 6 | { 7 | public PropertyInfo PropertyInfo { get; internal set; } 8 | public override Type DeclaringType => PropertyInfo.DeclaringType; 9 | public override bool CanWrite => PropertyInfo.CanWrite; 10 | public override bool IsStatic => m_isStatic ?? (bool)(m_isStatic = PropertyInfo.GetAccessors(true)[0].IsStatic); 11 | private bool? m_isStatic; 12 | 13 | public override bool ShouldAutoEvaluate => !HasArguments; 14 | 15 | public CacheProperty(PropertyInfo pi) 16 | { 17 | this.PropertyInfo = pi; 18 | } 19 | 20 | public override void SetInspectorOwner(ReflectionInspector inspector, MemberInfo member) 21 | { 22 | base.SetInspectorOwner(inspector, member); 23 | 24 | Arguments = PropertyInfo.GetIndexParameters(); 25 | } 26 | 27 | protected override object TryEvaluate() 28 | { 29 | try 30 | { 31 | object ret; 32 | if (HasArguments) 33 | ret = PropertyInfo.GetValue(DeclaringInstance, this.Evaluator.TryParseArguments()); 34 | else 35 | ret = PropertyInfo.GetValue(DeclaringInstance, null); 36 | LastException = null; 37 | return ret; 38 | } 39 | catch (Exception ex) 40 | { 41 | LastException = ex; 42 | return null; 43 | } 44 | } 45 | 46 | protected override void TrySetValue(object value) 47 | { 48 | if (!CanWrite) 49 | return; 50 | 51 | try 52 | { 53 | bool _static = PropertyInfo.GetAccessors(true)[0].IsStatic; 54 | 55 | if (HasArguments) 56 | PropertyInfo.SetValue(DeclaringInstance, value, Evaluator.TryParseArguments()); 57 | else 58 | PropertyInfo.SetValue(DeclaringInstance, value, null); 59 | } 60 | catch (Exception ex) 61 | { 62 | ExplorerCore.LogWarning(ex); 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/CacheObject/ICacheObjectController.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityExplorer.CacheObject.Views; 3 | 4 | namespace UnityExplorer.CacheObject 5 | { 6 | public interface ICacheObjectController 7 | { 8 | CacheObjectBase ParentCacheObject { get; } 9 | 10 | object Target { get; } 11 | Type TargetType { get; } 12 | 13 | bool CanWrite { get; } 14 | } 15 | 16 | public static class CacheObjectControllerHelper 17 | { 18 | // Helper so that this doesn't need to be copy+pasted between each implementation of the interface 19 | 20 | public static void SetCell(CacheObjectCell cell, int index, IList cachedEntries, Action onDataSetToCell) 21 | { 22 | if (index < 0 || index >= cachedEntries.Count) 23 | { 24 | if (cell.Occupant != null) 25 | cell.Occupant.UnlinkFromView(); 26 | 27 | cell.Disable(); 28 | return; 29 | } 30 | 31 | CacheObjectBase entry = (CacheObjectBase)cachedEntries[index]; 32 | 33 | if (entry.CellView != null && entry.CellView != cell) 34 | entry.UnlinkFromView(); 35 | 36 | if (cell.Occupant != null && cell.Occupant != entry) 37 | cell.Occupant.UnlinkFromView(); 38 | 39 | if (entry.CellView != cell) 40 | entry.SetView(cell); 41 | 42 | entry.SetDataToCell(cell); 43 | 44 | onDataSetToCell?.Invoke(cell); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/CacheObject/IValues/InteractiveString.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Config; 2 | using UniverseLib.UI; 3 | using UniverseLib.UI.Models; 4 | 5 | namespace UnityExplorer.CacheObject.IValues 6 | { 7 | public class InteractiveString : InteractiveValue 8 | { 9 | private string RealValue; 10 | public string EditedValue = ""; 11 | 12 | public InputFieldRef inputField; 13 | public ButtonRef ApplyButton; 14 | 15 | public GameObject SaveFileRow; 16 | public InputFieldRef SaveFilePath; 17 | 18 | public override void OnBorrowed(CacheObjectBase owner) 19 | { 20 | base.OnBorrowed(owner); 21 | 22 | bool canWrite = owner.CanWrite && owner.State != ValueState.Exception; 23 | inputField.Component.readOnly = !canWrite; 24 | ApplyButton.Component.gameObject.SetActive(canWrite); 25 | 26 | SaveFilePath.Text = Path.Combine(ConfigManager.Default_Output_Path.Value, "untitled.txt"); 27 | } 28 | 29 | private bool IsStringTooLong(string s) 30 | { 31 | if (s == null) 32 | return false; 33 | 34 | return s.Length >= UniversalUI.MAX_INPUTFIELD_CHARS; 35 | } 36 | 37 | public override void SetValue(object value) 38 | { 39 | if (CurrentOwner.State == ValueState.Exception) 40 | value = CurrentOwner.LastException.ToString(); 41 | 42 | RealValue = value as string; 43 | SaveFileRow.SetActive(IsStringTooLong(RealValue)); 44 | 45 | if (value == null) 46 | { 47 | inputField.Text = ""; 48 | EditedValue = ""; 49 | } 50 | else 51 | { 52 | EditedValue = (string)value; 53 | inputField.Text = EditedValue; 54 | } 55 | } 56 | 57 | private void OnApplyClicked() 58 | { 59 | CurrentOwner.SetUserValue(EditedValue); 60 | } 61 | 62 | private void OnInputChanged(string input) 63 | { 64 | EditedValue = input; 65 | SaveFileRow.SetActive(IsStringTooLong(EditedValue)); 66 | } 67 | 68 | private void OnSaveFileClicked() 69 | { 70 | if (RealValue == null) 71 | return; 72 | 73 | if (string.IsNullOrEmpty(SaveFilePath.Text)) 74 | { 75 | ExplorerCore.LogWarning("Cannot save an empty file path!"); 76 | return; 77 | } 78 | 79 | string path = IOUtility.EnsureValidFilePath(SaveFilePath.Text); 80 | 81 | if (File.Exists(path)) 82 | File.Delete(path); 83 | 84 | File.WriteAllText(path, RealValue); 85 | } 86 | 87 | public override GameObject CreateContent(GameObject parent) 88 | { 89 | UIRoot = UIFactory.CreateVerticalGroup(parent, "InteractiveString", false, false, true, true, 3, new Vector4(4, 4, 4, 4), 90 | new Color(0.06f, 0.06f, 0.06f)); 91 | 92 | // Save to file helper 93 | 94 | SaveFileRow = UIFactory.CreateUIObject("SaveFileRow", UIRoot); 95 | UIFactory.SetLayoutElement(SaveFileRow, flexibleWidth: 9999); 96 | UIFactory.SetLayoutGroup(SaveFileRow, false, true, true, true, 3); 97 | 98 | UIFactory.CreateLabel(SaveFileRow, "Info", "String is too long! Save to file if you want to see the full string.", 99 | TextAnchor.MiddleLeft); 100 | 101 | GameObject horizRow = UIFactory.CreateUIObject("Horiz", SaveFileRow); 102 | UIFactory.SetLayoutGroup(horizRow, false, false, true, true, 4); 103 | 104 | ButtonRef saveButton = UIFactory.CreateButton(horizRow, "SaveButton", "Save file"); 105 | UIFactory.SetLayoutElement(saveButton.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); 106 | saveButton.OnClick += OnSaveFileClicked; 107 | 108 | SaveFilePath = UIFactory.CreateInputField(horizRow, "SaveInput", "..."); 109 | UIFactory.SetLayoutElement(SaveFilePath.UIRoot, minHeight: 25, flexibleWidth: 9999); 110 | 111 | // Main Input / apply 112 | 113 | ApplyButton = UIFactory.CreateButton(UIRoot, "ApplyButton", "Apply", new Color(0.2f, 0.27f, 0.2f)); 114 | UIFactory.SetLayoutElement(ApplyButton.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); 115 | ApplyButton.OnClick += OnApplyClicked; 116 | 117 | inputField = UIFactory.CreateInputField(UIRoot, "InputField", "empty"); 118 | inputField.UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; 119 | UIFactory.SetLayoutElement(inputField.UIRoot, minHeight: 25, flexibleHeight: 500, flexibleWidth: 9999); 120 | inputField.Component.lineType = InputField.LineType.MultiLineNewline; 121 | inputField.OnValueChanged += OnInputChanged; 122 | 123 | return UIRoot; 124 | } 125 | 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/CacheObject/IValues/InteractiveValue.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI.ObjectPool; 2 | 3 | namespace UnityExplorer.CacheObject.IValues 4 | { 5 | public abstract class InteractiveValue : IPooledObject 6 | { 7 | public static Type GetIValueTypeForState(ValueState state) 8 | { 9 | return state switch 10 | { 11 | ValueState.Exception or ValueState.String => typeof(InteractiveString), 12 | ValueState.Enum => typeof(InteractiveEnum), 13 | ValueState.Collection => typeof(InteractiveList), 14 | ValueState.Dictionary => typeof(InteractiveDictionary), 15 | ValueState.ValueStruct => typeof(InteractiveValueStruct), 16 | ValueState.Color => typeof(InteractiveColor), 17 | _ => null, 18 | }; 19 | } 20 | 21 | public GameObject UIRoot { get; set; } 22 | public float DefaultHeight => -1f; 23 | 24 | public virtual bool CanWrite => this.CurrentOwner.CanWrite; 25 | 26 | public CacheObjectBase CurrentOwner => owner; 27 | private CacheObjectBase owner; 28 | 29 | public bool PendingValueWanted; 30 | 31 | public virtual void OnBorrowed(CacheObjectBase owner) 32 | { 33 | if (this.owner != null) 34 | { 35 | ExplorerCore.LogWarning("Setting an IValue's owner but there is already one set. Maybe it wasn't cleaned up?"); 36 | ReleaseFromOwner(); 37 | } 38 | 39 | this.owner = owner; 40 | } 41 | 42 | public virtual void ReleaseFromOwner() 43 | { 44 | if (this.owner == null) 45 | return; 46 | 47 | this.owner = null; 48 | } 49 | 50 | public abstract void SetValue(object value); 51 | 52 | public virtual void SetLayout() { } 53 | 54 | public abstract GameObject CreateContent(GameObject parent); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/CacheObject/Views/CacheConfigCell.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI; 2 | 3 | namespace UnityExplorer.CacheObject.Views 4 | { 5 | public class ConfigEntryCell : CacheObjectCell 6 | { 7 | public override GameObject CreateContent(GameObject parent) 8 | { 9 | // Main layout 10 | 11 | UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30)); 12 | Rect = UIRoot.GetComponent(); 13 | UIFactory.SetLayoutGroup(UIRoot, false, false, true, true, 4, 4, 4, 4, 4, childAlignment: TextAnchor.UpperLeft); 14 | UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600); 15 | UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; 16 | 17 | // Left label 18 | 19 | NameLabel = UIFactory.CreateLabel(UIRoot, "NameLabel", "", TextAnchor.MiddleLeft); 20 | NameLabel.horizontalOverflow = HorizontalWrapMode.Wrap; 21 | UIFactory.SetLayoutElement(NameLabel.gameObject, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 300); 22 | NameLayout = NameLabel.GetComponent(); 23 | 24 | // horizontal group 25 | 26 | GameObject horiGroup = UIFactory.CreateUIObject("RightHoriGroup", UIRoot); 27 | UIFactory.SetLayoutGroup(horiGroup, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft); 28 | UIFactory.SetLayoutElement(horiGroup, minHeight: 25, minWidth: 200, flexibleWidth: 9999, flexibleHeight: 800); 29 | 30 | SubContentButton = UIFactory.CreateButton(horiGroup, "SubContentButton", "▲", subInactiveColor); 31 | UIFactory.SetLayoutElement(SubContentButton.Component.gameObject, minWidth: 25, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0); 32 | SubContentButton.OnClick += SubContentClicked; 33 | 34 | // Type label 35 | 36 | TypeLabel = UIFactory.CreateLabel(horiGroup, "TypeLabel", "", TextAnchor.MiddleLeft); 37 | TypeLabel.horizontalOverflow = HorizontalWrapMode.Wrap; 38 | UIFactory.SetLayoutElement(TypeLabel.gameObject, minHeight: 25, flexibleHeight: 150, minWidth: 60, flexibleWidth: 0); 39 | 40 | // Bool and number value interaction 41 | 42 | GameObject toggleObj = UIFactory.CreateToggle(horiGroup, "Toggle", out Toggle, out ToggleText); 43 | UIFactory.SetLayoutElement(toggleObj, minWidth: 70, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0); 44 | ToggleText.color = SignatureHighlighter.KeywordBlue; 45 | Toggle.onValueChanged.AddListener(ToggleClicked); 46 | 47 | InputField = UIFactory.CreateInputField(horiGroup, "InputField", "..."); 48 | UIFactory.SetLayoutElement(InputField.UIRoot, minWidth: 150, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); 49 | 50 | // Apply 51 | 52 | ApplyButton = UIFactory.CreateButton(horiGroup, "ApplyButton", "Apply", new Color(0.15f, 0.19f, 0.15f)); 53 | UIFactory.SetLayoutElement(ApplyButton.Component.gameObject, minWidth: 70, minHeight: 25, flexibleWidth: 0, flexibleHeight: 0); 54 | ApplyButton.OnClick += ApplyClicked; 55 | 56 | // Main value label 57 | 58 | ValueLabel = UIFactory.CreateLabel(horiGroup, "ValueLabel", "Value goes here", TextAnchor.MiddleLeft); 59 | ValueLabel.horizontalOverflow = HorizontalWrapMode.Wrap; 60 | UIFactory.SetLayoutElement(ValueLabel.gameObject, minHeight: 25, flexibleHeight: 150, flexibleWidth: 9999); 61 | 62 | // Subcontent 63 | 64 | SubContentHolder = UIFactory.CreateUIObject("SubContent", UIRoot); 65 | UIFactory.SetLayoutElement(SubContentHolder.gameObject, minHeight: 30, flexibleHeight: 600, minWidth: 100, flexibleWidth: 9999); 66 | UIFactory.SetLayoutGroup(SubContentHolder, true, true, true, true, 2, childAlignment: TextAnchor.UpperLeft); 67 | //SubContentHolder.AddComponent().verticalFit = ContentSizeFitter.FitMode.MinSize; 68 | SubContentHolder.SetActive(false); 69 | 70 | // Bottom separator 71 | GameObject separator = UIFactory.CreateUIObject("BottomSeperator", UIRoot); 72 | UIFactory.SetLayoutElement(separator, minHeight: 1, flexibleHeight: 0, flexibleWidth: 9999); 73 | separator.AddComponent().color = Color.black; 74 | 75 | return UIRoot; 76 | } 77 | 78 | protected override void ConstructEvaluateHolder(GameObject parent) { } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/CacheObject/Views/CacheKeyValuePairCell.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.CacheObject.IValues; 2 | using UniverseLib.UI; 3 | using UniverseLib.UI.Models; 4 | 5 | namespace UnityExplorer.CacheObject.Views 6 | { 7 | public class CacheKeyValuePairCell : CacheObjectCell 8 | { 9 | public Image Image { get; private set; } 10 | public InteractiveDictionary DictOwner => Occupant.Owner as InteractiveDictionary; 11 | 12 | public LayoutElement KeyGroupLayout; 13 | public Text KeyLabel; 14 | public ButtonRef KeyInspectButton; 15 | public InputFieldRef KeyInputField; 16 | public Text KeyInputTypeLabel; 17 | 18 | public static Color EvenColor = new(0.07f, 0.07f, 0.07f); 19 | public static Color OddColor = new(0.063f, 0.063f, 0.063f); 20 | 21 | public int AdjustedWidth => (int)Rect.rect.width - 70; 22 | 23 | //public int HalfWidth => (int)(0.5f * Rect.rect.width) - 75; 24 | //public int AdjustedKeyWidth => HalfWidth - 50; 25 | //public int AdjustedRightWidth => HalfWidth; 26 | 27 | private void KeyInspectClicked() 28 | { 29 | InspectorManager.Inspect((Occupant as CacheKeyValuePair).DictKey, this.Occupant); 30 | } 31 | 32 | public override GameObject CreateContent(GameObject parent) 33 | { 34 | GameObject root = base.CreateContent(parent); 35 | 36 | Image = root.AddComponent(); 37 | 38 | this.NameLayout.minWidth = 70; 39 | this.NameLayout.flexibleWidth = 0; 40 | this.NameLayout.minHeight = 30; 41 | this.NameLayout.flexibleHeight = 0; 42 | this.NameLabel.alignment = TextAnchor.MiddleRight; 43 | 44 | this.RightGroupLayout.minWidth = AdjustedWidth * 0.55f; 45 | 46 | // Key area 47 | GameObject keyGroup = UIFactory.CreateUIObject("KeyHolder", root.transform.Find("HoriGroup").gameObject); 48 | UIFactory.SetLayoutGroup(keyGroup, false, false, true, true, 2, 0, 0, 4, 4, childAlignment: TextAnchor.MiddleLeft); 49 | KeyGroupLayout = UIFactory.SetLayoutElement(keyGroup, minHeight: 30, minWidth: (int)(AdjustedWidth * 0.44f), flexibleWidth: 0); 50 | 51 | // set to be after the NameLabel (our index label), and before the main horizontal group. 52 | keyGroup.transform.SetSiblingIndex(1); 53 | 54 | // key Inspect 55 | 56 | KeyInspectButton = UIFactory.CreateButton(keyGroup, "KeyInspectButton", "Inspect", new Color(0.15f, 0.15f, 0.15f)); 57 | UIFactory.SetLayoutElement(KeyInspectButton.Component.gameObject, minWidth: 60, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); 58 | KeyInspectButton.OnClick += KeyInspectClicked; 59 | 60 | // label 61 | 62 | KeyLabel = UIFactory.CreateLabel(keyGroup, "KeyLabel", "empty", TextAnchor.MiddleLeft); 63 | UIFactory.SetLayoutElement(KeyLabel.gameObject, minWidth: 50, flexibleWidth: 999, minHeight: 25); 64 | 65 | // Type label for input field 66 | 67 | KeyInputTypeLabel = UIFactory.CreateLabel(keyGroup, "InputTypeLabel", "null", TextAnchor.MiddleLeft); 68 | UIFactory.SetLayoutElement(KeyInputTypeLabel.gameObject, minWidth: 55, flexibleWidth: 0, minHeight: 25, flexibleHeight: 0); 69 | 70 | // input field 71 | 72 | KeyInputField = UIFactory.CreateInputField(keyGroup, "KeyInput", "empty"); 73 | UIFactory.SetLayoutElement(KeyInputField.UIRoot, minHeight: 25, flexibleHeight: 0, flexibleWidth: 0, preferredWidth: 200); 74 | //KeyInputField.lineType = InputField.LineType.MultiLineNewline; 75 | KeyInputField.Component.readOnly = true; 76 | 77 | return root; 78 | } 79 | 80 | protected override void ConstructEvaluateHolder(GameObject parent) 81 | { 82 | // not used 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/CacheObject/Views/CacheListEntryCell.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.CacheObject.IValues; 2 | 3 | namespace UnityExplorer.CacheObject.Views 4 | { 5 | public class CacheListEntryCell : CacheObjectCell 6 | { 7 | public Image Image { get; private set; } 8 | public InteractiveList ListOwner => Occupant.Owner as InteractiveList; 9 | 10 | public static Color EvenColor = new(0.12f, 0.12f, 0.12f); 11 | public static Color OddColor = new(0.1f, 0.1f, 0.1f); 12 | 13 | public override GameObject CreateContent(GameObject parent) 14 | { 15 | GameObject root = base.CreateContent(parent); 16 | 17 | Image = root.AddComponent(); 18 | 19 | this.NameLayout.minWidth = 40; 20 | this.NameLayout.flexibleWidth = 50; 21 | this.NameLayout.minHeight = 25; 22 | this.NameLayout.flexibleHeight = 0; 23 | this.NameLabel.alignment = TextAnchor.MiddleRight; 24 | 25 | return root; 26 | } 27 | 28 | protected override void ConstructEvaluateHolder(GameObject parent) 29 | { 30 | // not used 31 | } 32 | 33 | //protected override void ConstructUpdateToggle(GameObject parent) 34 | //{ 35 | // // not used 36 | //} 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/CacheObject/Views/CacheMemberCell.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI; 2 | using UniverseLib.UI.Models; 3 | 4 | namespace UnityExplorer.CacheObject.Views 5 | { 6 | public class CacheMemberCell : CacheObjectCell 7 | { 8 | public CacheMember MemberOccupant => Occupant as CacheMember; 9 | 10 | public GameObject EvaluateHolder; 11 | public ButtonRef EvaluateButton; 12 | 13 | protected virtual void EvaluateClicked() 14 | { 15 | this.MemberOccupant.OnEvaluateClicked(); 16 | } 17 | 18 | protected override void ConstructEvaluateHolder(GameObject parent) 19 | { 20 | // Evaluate vert group 21 | 22 | EvaluateHolder = UIFactory.CreateUIObject("EvalGroup", parent); 23 | UIFactory.SetLayoutGroup(EvaluateHolder, false, false, true, true, 3); 24 | UIFactory.SetLayoutElement(EvaluateHolder, minHeight: 25, flexibleWidth: 9999, flexibleHeight: 775); 25 | 26 | EvaluateButton = UIFactory.CreateButton(EvaluateHolder, "EvaluateButton", "Evaluate", new Color(0.15f, 0.15f, 0.15f)); 27 | UIFactory.SetLayoutElement(EvaluateButton.Component.gameObject, minWidth: 100, minHeight: 25); 28 | EvaluateButton.OnClick += EvaluateClicked; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/Config/ConfigElement.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.Config 2 | { 3 | public class ConfigElement : IConfigElement 4 | { 5 | public string Name { get; } 6 | public string Description { get; } 7 | 8 | public bool IsInternal { get; } 9 | public Type ElementType => typeof(T); 10 | 11 | public Action OnValueChanged; 12 | public Action OnValueChangedNotify { get; set; } 13 | 14 | public object DefaultValue { get; } 15 | 16 | public ConfigHandler Handler => IsInternal 17 | ? ConfigManager.InternalHandler 18 | : ConfigManager.Handler; 19 | 20 | public T Value 21 | { 22 | get => m_value; 23 | set => SetValue(value); 24 | } 25 | private T m_value; 26 | 27 | object IConfigElement.BoxedValue 28 | { 29 | get => m_value; 30 | set => SetValue((T)value); 31 | } 32 | 33 | public ConfigElement(string name, string description, T defaultValue, bool isInternal = false) 34 | { 35 | Name = name; 36 | Description = description; 37 | 38 | m_value = defaultValue; 39 | DefaultValue = defaultValue; 40 | 41 | IsInternal = isInternal; 42 | 43 | ConfigManager.RegisterConfigElement(this); 44 | } 45 | 46 | private void SetValue(T value) 47 | { 48 | if ((m_value == null && value == null) || (m_value != null && m_value.Equals(value))) 49 | return; 50 | 51 | m_value = value; 52 | 53 | Handler.SetConfigValue(this, value); 54 | 55 | OnValueChanged?.Invoke(value); 56 | OnValueChangedNotify?.Invoke(); 57 | 58 | Handler.OnAnyConfigChanged(); 59 | } 60 | 61 | object IConfigElement.GetLoaderConfigValue() => GetLoaderConfigValue(); 62 | 63 | public T GetLoaderConfigValue() 64 | { 65 | return Handler.GetConfigValue(this); 66 | } 67 | 68 | public void RevertToDefaultValue() 69 | { 70 | Value = (T)DefaultValue; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/Config/ConfigHandler.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.Config 2 | { 3 | public abstract class ConfigHandler 4 | { 5 | public abstract void RegisterConfigElement(ConfigElement element); 6 | 7 | public abstract void SetConfigValue(ConfigElement element, T value); 8 | 9 | public abstract T GetConfigValue(ConfigElement element); 10 | 11 | public abstract void Init(); 12 | 13 | public abstract void LoadConfig(); 14 | 15 | public abstract void SaveConfig(); 16 | 17 | public virtual void OnAnyConfigChanged() { } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Config/IConfigElement.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.Config 2 | { 3 | public interface IConfigElement 4 | { 5 | string Name { get; } 6 | string Description { get; } 7 | 8 | bool IsInternal { get; } 9 | Type ElementType { get; } 10 | 11 | object BoxedValue { get; set; } 12 | object DefaultValue { get; } 13 | 14 | object GetLoaderConfigValue(); 15 | 16 | void RevertToDefaultValue(); 17 | 18 | Action OnValueChangedNotify { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Config/InternalConfigHandler.cs: -------------------------------------------------------------------------------- 1 | using Tomlet; 2 | using Tomlet.Models; 3 | using UnityExplorer.UI; 4 | 5 | namespace UnityExplorer.Config 6 | { 7 | public class InternalConfigHandler : ConfigHandler 8 | { 9 | internal static string CONFIG_PATH; 10 | 11 | public override void Init() 12 | { 13 | CONFIG_PATH = Path.Combine(ExplorerCore.ExplorerFolder, "data.cfg"); 14 | } 15 | 16 | public override void LoadConfig() 17 | { 18 | if (!TryLoadConfig()) 19 | SaveConfig(); 20 | } 21 | 22 | public override void RegisterConfigElement(ConfigElement element) 23 | { 24 | // Not necessary 25 | } 26 | 27 | public override void SetConfigValue(ConfigElement element, T value) 28 | { 29 | // Not necessary 30 | } 31 | 32 | // Not necessary, just return the value. 33 | public override T GetConfigValue(ConfigElement element) => element.Value; 34 | 35 | // Always just auto-save. 36 | public override void OnAnyConfigChanged() => SaveConfig(); 37 | 38 | public bool TryLoadConfig() 39 | { 40 | try 41 | { 42 | if (!File.Exists(CONFIG_PATH)) 43 | return false; 44 | 45 | TomlDocument document = TomlParser.ParseFile(CONFIG_PATH); 46 | foreach (string key in document.Keys) 47 | { 48 | if (!Enum.IsDefined(typeof(UIManager.Panels), key)) 49 | continue; 50 | 51 | UIManager.Panels panelKey = (UIManager.Panels)Enum.Parse(typeof(UIManager.Panels), key); 52 | ConfigManager.GetPanelSaveData(panelKey).Value = document.GetString(key); 53 | } 54 | 55 | return true; 56 | } 57 | catch (Exception ex) 58 | { 59 | ExplorerCore.LogWarning("Error loading internal data: " + ex.ToString()); 60 | return false; 61 | } 62 | } 63 | 64 | public override void SaveConfig() 65 | { 66 | if (UIManager.Initializing) 67 | return; 68 | 69 | TomlDocument tomlDocument = TomlDocument.CreateEmpty(); 70 | foreach (KeyValuePair entry in ConfigManager.InternalConfigs) 71 | tomlDocument.Put(entry.Key, entry.Value.BoxedValue as string, false); 72 | 73 | File.WriteAllText(CONFIG_PATH, tomlDocument.SerializedValue); 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /src/ExplorerBehaviour.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.UI; 2 | #if CPP 3 | #if UNHOLLOWER 4 | using UnhollowerRuntimeLib; 5 | #else 6 | using Il2CppInterop.Runtime.Injection; 7 | #endif 8 | #endif 9 | 10 | namespace UnityExplorer 11 | { 12 | public class ExplorerBehaviour : MonoBehaviour 13 | { 14 | internal static ExplorerBehaviour Instance { get; private set; } 15 | 16 | #if CPP 17 | public ExplorerBehaviour(System.IntPtr ptr) : base(ptr) { } 18 | #endif 19 | 20 | internal static void Setup() 21 | { 22 | #if CPP 23 | ClassInjector.RegisterTypeInIl2Cpp(); 24 | #endif 25 | 26 | GameObject obj = new("ExplorerBehaviour"); 27 | DontDestroyOnLoad(obj); 28 | obj.hideFlags = HideFlags.HideAndDontSave; 29 | Instance = obj.AddComponent(); 30 | } 31 | 32 | internal void Update() 33 | { 34 | ExplorerCore.Update(); 35 | } 36 | 37 | // For editor, to clean up objects 38 | 39 | internal void OnDestroy() 40 | { 41 | OnApplicationQuit(); 42 | } 43 | 44 | internal bool quitting; 45 | 46 | internal void OnApplicationQuit() 47 | { 48 | if (quitting) return; 49 | quitting = true; 50 | 51 | TryDestroy(UIManager.UIRoot?.transform.root.gameObject); 52 | 53 | TryDestroy((typeof(Universe).Assembly.GetType("UniverseLib.UniversalBehaviour") 54 | .GetProperty("Instance", BindingFlags.Static | BindingFlags.NonPublic) 55 | .GetValue(null, null) 56 | as Component).gameObject); 57 | 58 | TryDestroy(this.gameObject); 59 | } 60 | 61 | internal void TryDestroy(GameObject obj) 62 | { 63 | try 64 | { 65 | if (obj) 66 | Destroy(obj); 67 | } 68 | catch { } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/Hooks/AddHookCell.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI; 2 | using UniverseLib.UI.Models; 3 | using UniverseLib.UI.Widgets.ScrollView; 4 | 5 | namespace UnityExplorer.Hooks 6 | { 7 | public class AddHookCell : ICell 8 | { 9 | public bool Enabled => UIRoot.activeSelf; 10 | 11 | public RectTransform Rect { get; set; } 12 | public GameObject UIRoot { get; set; } 13 | 14 | public float DefaultHeight => 30; 15 | 16 | public Text MethodNameLabel; 17 | public ButtonRef HookButton; 18 | 19 | public int CurrentDisplayedIndex; 20 | 21 | private void OnHookClicked() 22 | { 23 | HookCreator.AddHookClicked(CurrentDisplayedIndex); 24 | } 25 | 26 | public void Enable() 27 | { 28 | this.UIRoot.SetActive(true); 29 | } 30 | 31 | public void Disable() 32 | { 33 | this.UIRoot.SetActive(false); 34 | } 35 | 36 | public GameObject CreateContent(GameObject parent) 37 | { 38 | UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30)); 39 | Rect = UIRoot.GetComponent(); 40 | UIFactory.SetLayoutGroup(UIRoot, false, false, true, true, 5, childAlignment: TextAnchor.UpperLeft); 41 | UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600); 42 | UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; 43 | 44 | HookButton = UIFactory.CreateButton(UIRoot, "HookButton", "Hook", new Color(0.2f, 0.25f, 0.2f)); 45 | UIFactory.SetLayoutElement(HookButton.Component.gameObject, minHeight: 25, minWidth: 100); 46 | HookButton.OnClick += OnHookClicked; 47 | 48 | MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft); 49 | UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999); 50 | 51 | return UIRoot; 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Hooks/HookCell.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI; 2 | using UniverseLib.UI.Models; 3 | using UniverseLib.UI.Widgets.ScrollView; 4 | 5 | namespace UnityExplorer.Hooks 6 | { 7 | public class HookCell : ICell 8 | { 9 | public bool Enabled => UIRoot.activeSelf; 10 | 11 | public RectTransform Rect { get; set; } 12 | public GameObject UIRoot { get; set; } 13 | 14 | public float DefaultHeight => 30; 15 | 16 | public Text MethodNameLabel; 17 | public ButtonRef EditPatchButton; 18 | public ButtonRef ToggleActiveButton; 19 | public ButtonRef DeleteButton; 20 | 21 | public int CurrentDisplayedIndex; 22 | 23 | private void OnToggleActiveClicked() 24 | { 25 | HookList.EnableOrDisableHookClicked(CurrentDisplayedIndex); 26 | } 27 | 28 | private void OnDeleteClicked() 29 | { 30 | HookList.DeleteHookClicked(CurrentDisplayedIndex); 31 | HookCreator.AddHooksScrollPool.Refresh(true, false); 32 | } 33 | 34 | private void OnEditPatchClicked() 35 | { 36 | HookList.EditPatchClicked(CurrentDisplayedIndex); 37 | } 38 | 39 | public GameObject CreateContent(GameObject parent) 40 | { 41 | UIRoot = UIFactory.CreateUIObject(this.GetType().Name, parent, new Vector2(100, 30)); 42 | Rect = UIRoot.GetComponent(); 43 | UIFactory.SetLayoutGroup(UIRoot, false, false, true, true, 4, childAlignment: TextAnchor.UpperLeft); 44 | UIFactory.SetLayoutElement(UIRoot, minWidth: 100, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 600); 45 | UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; 46 | 47 | MethodNameLabel = UIFactory.CreateLabel(UIRoot, "MethodName", "NOT SET", TextAnchor.MiddleLeft); 48 | UIFactory.SetLayoutElement(MethodNameLabel.gameObject, minHeight: 25, flexibleWidth: 9999); 49 | 50 | ToggleActiveButton = UIFactory.CreateButton(UIRoot, "ToggleActiveBtn", "On", new Color(0.15f, 0.2f, 0.15f)); 51 | UIFactory.SetLayoutElement(ToggleActiveButton.Component.gameObject, minHeight: 25, minWidth: 35); 52 | ToggleActiveButton.OnClick += OnToggleActiveClicked; 53 | 54 | EditPatchButton = UIFactory.CreateButton(UIRoot, "EditButton", "Edit", new Color(0.15f, 0.15f, 0.15f)); 55 | UIFactory.SetLayoutElement(EditPatchButton.Component.gameObject, minHeight: 25, minWidth: 35); 56 | EditPatchButton.OnClick += OnEditPatchClicked; 57 | 58 | DeleteButton = UIFactory.CreateButton(UIRoot, "DeleteButton", "X", new Color(0.2f, 0.15f, 0.15f)); 59 | UIFactory.SetLayoutElement(DeleteButton.Component.gameObject, minHeight: 25, minWidth: 35); 60 | DeleteButton.OnClick += OnDeleteClicked; 61 | 62 | return UIRoot; 63 | } 64 | 65 | public void Disable() 66 | { 67 | UIRoot.SetActive(false); 68 | } 69 | 70 | public void Enable() 71 | { 72 | UIRoot.SetActive(true); 73 | } 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/Hooks/HookList.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using System.Collections.Specialized; 3 | using UnityExplorer.UI.Panels; 4 | using UniverseLib.UI; 5 | using UniverseLib.UI.Widgets.ScrollView; 6 | 7 | namespace UnityExplorer.Hooks 8 | { 9 | public class HookList : ICellPoolDataSource 10 | { 11 | public int ItemCount => currentHooks.Count; 12 | 13 | internal static readonly HashSet hookedSignatures = new(); 14 | internal static readonly OrderedDictionary currentHooks = new(); 15 | 16 | internal static GameObject UIRoot; 17 | internal static ScrollPool HooksScrollPool; 18 | 19 | public static void EnableOrDisableHookClicked(int index) 20 | { 21 | HookInstance hook = (HookInstance)currentHooks[index]; 22 | hook.TogglePatch(); 23 | 24 | HooksScrollPool.Refresh(true, false); 25 | } 26 | 27 | public static void DeleteHookClicked(int index) 28 | { 29 | HookInstance hook = (HookInstance)currentHooks[index]; 30 | 31 | if (HookCreator.CurrentEditedHook == hook) 32 | HookCreator.EditorInputCancel(); 33 | 34 | hook.Unpatch(); 35 | currentHooks.RemoveAt(index); 36 | hookedSignatures.Remove(hook.TargetMethod.FullDescription()); 37 | 38 | HooksScrollPool.Refresh(true, false); 39 | } 40 | 41 | public static void EditPatchClicked(int index) 42 | { 43 | if (HookCreator.PendingGeneric) 44 | HookManagerPanel.genericArgsHandler.Cancel(); 45 | 46 | HookManagerPanel.Instance.SetPage(HookManagerPanel.Pages.HookSourceEditor); 47 | HookInstance hook = (HookInstance)currentHooks[index]; 48 | HookCreator.SetEditedHook(hook); 49 | } 50 | 51 | // Set current hook cell 52 | 53 | public void OnCellBorrowed(HookCell cell) { } 54 | 55 | public void SetCell(HookCell cell, int index) 56 | { 57 | if (index >= currentHooks.Count) 58 | { 59 | cell.Disable(); 60 | return; 61 | } 62 | 63 | cell.CurrentDisplayedIndex = index; 64 | HookInstance hook = (HookInstance)currentHooks[index]; 65 | 66 | cell.MethodNameLabel.text = SignatureHighlighter.ParseMethod(hook.TargetMethod); 67 | 68 | cell.ToggleActiveButton.ButtonText.text = hook.Enabled ? "On" : "Off"; 69 | RuntimeHelper.SetColorBlockAuto(cell.ToggleActiveButton.Component, 70 | hook.Enabled ? new Color(0.15f, 0.2f, 0.15f) : new Color(0.2f, 0.2f, 0.15f)); 71 | } 72 | 73 | // UI 74 | 75 | internal void ConstructUI(GameObject leftGroup) 76 | { 77 | UIRoot = UIFactory.CreateUIObject("CurrentHooksPanel", leftGroup); 78 | UIFactory.SetLayoutElement(UIRoot, preferredHeight: 150, flexibleHeight: 0, flexibleWidth: 9999); 79 | UIFactory.SetLayoutGroup(UIRoot, true, true, true, true); 80 | 81 | Text hooksLabel = UIFactory.CreateLabel(UIRoot, "HooksLabel", "Current Hooks", TextAnchor.MiddleCenter); 82 | UIFactory.SetLayoutElement(hooksLabel.gameObject, minHeight: 30, flexibleWidth: 9999); 83 | 84 | HooksScrollPool = UIFactory.CreateScrollPool(UIRoot, "HooksScrollPool", 85 | out GameObject hooksScroll, out GameObject hooksContent); 86 | UIFactory.SetLayoutElement(hooksScroll, flexibleHeight: 9999); 87 | HooksScrollPool.Initialize(this); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Inspectors/InspectorBase.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.UI.Panels; 2 | using UniverseLib.UI.ObjectPool; 3 | 4 | namespace UnityExplorer.Inspectors 5 | { 6 | public abstract class InspectorBase : IPooledObject 7 | { 8 | public bool IsActive { get; internal set; } 9 | public object Target { get; set; } 10 | public Type TargetType { get; protected set; } 11 | 12 | public InspectorTab Tab { get; internal set; } 13 | 14 | public GameObject UIRoot { get; set; } 15 | 16 | public float DefaultHeight => -1f; 17 | public abstract GameObject CreateContent(GameObject parent); 18 | 19 | public abstract void Update(); 20 | 21 | public abstract void CloseInspector(); 22 | 23 | public virtual void OnBorrowedFromPool(object target) 24 | { 25 | this.Target = target; 26 | this.TargetType = target is Type type ? type : target.GetActualType(); 27 | 28 | Tab = Pool.Borrow(); 29 | Tab.UIRoot.transform.SetParent(InspectorPanel.Instance.NavbarHolder.transform, false); 30 | 31 | Tab.TabButton.OnClick += OnTabButtonClicked; 32 | Tab.CloseButton.OnClick += CloseInspector; 33 | } 34 | 35 | public virtual void OnReturnToPool() 36 | { 37 | Pool.Return(Tab); 38 | 39 | this.Target = null; 40 | 41 | Tab.TabButton.OnClick -= OnTabButtonClicked; 42 | Tab.CloseButton.OnClick -= CloseInspector; 43 | } 44 | 45 | public virtual void OnSetActive() 46 | { 47 | Tab.SetTabColor(true); 48 | UIRoot.SetActive(true); 49 | IsActive = true; 50 | LayoutRebuilder.ForceRebuildLayoutImmediate(UIRoot.GetComponent()); 51 | } 52 | 53 | public virtual void OnSetInactive() 54 | { 55 | Tab.SetTabColor(false); 56 | UIRoot.SetActive(false); 57 | IsActive = false; 58 | } 59 | 60 | private void OnTabButtonClicked() 61 | { 62 | InspectorManager.SetInspectorActive(this); 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /src/Inspectors/InspectorTab.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI; 2 | using UniverseLib.UI.Models; 3 | using UniverseLib.UI.ObjectPool; 4 | 5 | namespace UnityExplorer.Inspectors 6 | { 7 | public class InspectorTab : IPooledObject 8 | { 9 | public GameObject UIRoot { get; set; } 10 | public float DefaultHeight => 25f; 11 | 12 | public ButtonRef TabButton; 13 | public Text TabText; 14 | public ButtonRef CloseButton; 15 | 16 | private static readonly Color enabledTabColor = new(0.15f, 0.22f, 0.15f); 17 | private static readonly Color disabledTabColor = new(0.13f, 0.13f, 0.13f); 18 | 19 | public void SetTabColor(bool active) 20 | { 21 | Color color = active ? enabledTabColor : disabledTabColor; 22 | RuntimeHelper.SetColorBlock(TabButton.Component, color, color * 1.2f); 23 | } 24 | 25 | public GameObject CreateContent(GameObject parent) 26 | { 27 | UIRoot = UIFactory.CreateHorizontalGroup(parent, "TabObject", false, true, true, true, 1, 28 | default, new Color(0.13f, 0.13f, 0.13f), childAlignment: TextAnchor.MiddleLeft); 29 | UIFactory.SetLayoutElement(UIRoot, minWidth: 200, flexibleWidth: 0); 30 | UIRoot.AddComponent(); 31 | UIRoot.AddComponent(); 32 | 33 | TabButton = UIFactory.CreateButton(UIRoot, "TabButton", ""); 34 | UIFactory.SetLayoutElement(TabButton.Component.gameObject, minWidth: 173, flexibleWidth: 0); 35 | UIFactory.SetLayoutGroup(TabButton.Component.gameObject, false, false, true, true, 0, 0, 0, 3); 36 | TabButton.GameObject.AddComponent(); 37 | 38 | TabText = TabButton.ButtonText; 39 | UIFactory.SetLayoutElement(TabText.gameObject, minHeight: 25, minWidth: 150, flexibleWidth: 0); 40 | TabText.alignment = TextAnchor.MiddleLeft; 41 | TabText.fontSize = 12; 42 | TabText.horizontalOverflow = HorizontalWrapMode.Overflow; 43 | 44 | CloseButton = UIFactory.CreateButton(UIRoot, "CloseButton", "X", new Color(0.15f, 0.15f, 0.15f, 1)); 45 | UIFactory.SetLayoutElement(CloseButton.Component.gameObject, minHeight: 25, minWidth: 25, flexibleWidth: 0); 46 | CloseButton.ButtonText.color = Color.red; 47 | 48 | return UIRoot; 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/Inspectors/MouseInspectors/MouseInspectorBase.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.Inspectors.MouseInspectors 2 | { 3 | public abstract class MouseInspectorBase 4 | { 5 | public abstract void OnBeginMouseInspect(); 6 | 7 | public abstract void UpdateMouseInspect(Vector2 mousePos); 8 | 9 | public abstract void OnSelectMouseInspect(); 10 | 11 | public abstract void ClearHitData(); 12 | 13 | public abstract void OnEndInspect(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/Inspectors/MouseInspectors/WorldInspector.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.Inspectors.MouseInspectors 2 | { 3 | public class WorldInspector : MouseInspectorBase 4 | { 5 | private static Camera MainCamera; 6 | private static GameObject lastHitObject; 7 | 8 | public override void OnBeginMouseInspect() 9 | { 10 | MainCamera = Camera.main; 11 | 12 | if (!MainCamera) 13 | { 14 | ExplorerCore.LogWarning("No MainCamera found! Cannot inspect world!"); 15 | return; 16 | } 17 | } 18 | 19 | public override void ClearHitData() 20 | { 21 | lastHitObject = null; 22 | } 23 | 24 | public override void OnSelectMouseInspect() 25 | { 26 | InspectorManager.Inspect(lastHitObject); 27 | } 28 | 29 | public override void UpdateMouseInspect(Vector2 mousePos) 30 | { 31 | if (!MainCamera) 32 | MainCamera = Camera.main; 33 | if (!MainCamera) 34 | { 35 | ExplorerCore.LogWarning("No Main Camera was found, unable to inspect world!"); 36 | MouseInspector.Instance.StopInspect(); 37 | return; 38 | } 39 | 40 | Ray ray = MainCamera.ScreenPointToRay(mousePos); 41 | Physics.Raycast(ray, out RaycastHit hit, 1000f); 42 | 43 | if (hit.transform) 44 | OnHitGameObject(hit.transform.gameObject); 45 | else if (lastHitObject) 46 | MouseInspector.Instance.ClearHitData(); 47 | } 48 | 49 | internal void OnHitGameObject(GameObject obj) 50 | { 51 | if (obj != lastHitObject) 52 | { 53 | lastHitObject = obj; 54 | MouseInspector.Instance.objNameLabel.text = $"Click to Inspect: {obj.name}"; 55 | MouseInspector.Instance.objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}"; 56 | } 57 | } 58 | 59 | public override void OnEndInspect() 60 | { 61 | // not needed 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/Loader/BepInEx/BepInExConfigHandler.cs: -------------------------------------------------------------------------------- 1 | #if BIE 2 | using BepInEx.Configuration; 3 | using UnityExplorer.Config; 4 | 5 | namespace UnityExplorer.Loader.BIE 6 | { 7 | public class BepInExConfigHandler : ConfigHandler 8 | { 9 | private ConfigFile Config => ExplorerBepInPlugin.Instance.Config; 10 | 11 | private const string CTG_NAME = "UnityExplorer"; 12 | 13 | public override void Init() 14 | { 15 | // Not necessary 16 | } 17 | 18 | public override void RegisterConfigElement(ConfigElement config) 19 | { 20 | ConfigEntry entry = Config.Bind(CTG_NAME, config.Name, config.Value, config.Description); 21 | 22 | entry.SettingChanged += (object o, EventArgs e) => 23 | { 24 | config.Value = entry.Value; 25 | }; 26 | } 27 | 28 | public override T GetConfigValue(ConfigElement element) 29 | { 30 | if (Config.TryGetEntry(CTG_NAME, element.Name, out ConfigEntry configEntry)) 31 | return configEntry.Value; 32 | else 33 | throw new Exception("Could not get config entry '" + element.Name + "'"); 34 | } 35 | 36 | public override void SetConfigValue(ConfigElement element, T value) 37 | { 38 | if (Config.TryGetEntry(CTG_NAME, element.Name, out ConfigEntry configEntry)) 39 | configEntry.Value = value; 40 | else 41 | ExplorerCore.Log("Could not get config entry '" + element.Name + "'"); 42 | } 43 | 44 | public override void LoadConfig() 45 | { 46 | foreach (KeyValuePair entry in ConfigManager.ConfigElements) 47 | { 48 | string key = entry.Key; 49 | ConfigDefinition def = new(CTG_NAME, key); 50 | if (Config.ContainsKey(def) && Config[def] is ConfigEntryBase configEntry) 51 | { 52 | IConfigElement config = entry.Value; 53 | config.BoxedValue = configEntry.BoxedValue; 54 | } 55 | } 56 | } 57 | 58 | public override void SaveConfig() 59 | { 60 | Config.Save(); 61 | } 62 | } 63 | } 64 | 65 | #endif -------------------------------------------------------------------------------- /src/Loader/BepInEx/ExplorerBepInPlugin.cs: -------------------------------------------------------------------------------- 1 | #if BIE 2 | using BepInEx; 3 | using BepInEx.Logging; 4 | using HarmonyLib; 5 | using UnityExplorer.Config; 6 | using UnityExplorer.Loader.BIE; 7 | #if CPP 8 | using BepInEx.IL2CPP; 9 | #endif 10 | 11 | namespace UnityExplorer 12 | { 13 | [BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)] 14 | 15 | public class ExplorerBepInPlugin : 16 | #if MONO 17 | BaseUnityPlugin 18 | #else 19 | BasePlugin 20 | #endif 21 | , IExplorerLoader 22 | { 23 | public static ExplorerBepInPlugin Instance; 24 | 25 | public ManualLogSource LogSource 26 | #if MONO 27 | => Logger; 28 | #else 29 | => Log; 30 | #endif 31 | const string IL2CPP_LIBS_FOLDER = 32 | #if UNHOLLOWER 33 | "unhollowed" 34 | #else 35 | "interop" 36 | #endif 37 | ; 38 | public string UnhollowedModulesFolder => Path.Combine(Paths.BepInExRootPath, IL2CPP_LIBS_FOLDER); 39 | 40 | public ConfigHandler ConfigHandler => _configHandler; 41 | private BepInExConfigHandler _configHandler; 42 | 43 | public Harmony HarmonyInstance => s_harmony; 44 | private static readonly Harmony s_harmony = new(ExplorerCore.GUID); 45 | 46 | public string ExplorerFolderName => ExplorerCore.DEFAULT_EXPLORER_FOLDER_NAME; 47 | public string ExplorerFolderDestination => Paths.PluginPath; 48 | 49 | public Action OnLogMessage => LogSource.LogMessage; 50 | public Action OnLogWarning => LogSource.LogWarning; 51 | public Action OnLogError => LogSource.LogError; 52 | 53 | private void Init() 54 | { 55 | Instance = this; 56 | _configHandler = new BepInExConfigHandler(); 57 | ExplorerCore.Init(this); 58 | } 59 | 60 | #if MONO // Mono 61 | internal void Awake() 62 | { 63 | Init(); 64 | } 65 | 66 | #else // Il2Cpp 67 | public override void Load() 68 | { 69 | Init(); 70 | } 71 | #endif 72 | } 73 | } 74 | #endif -------------------------------------------------------------------------------- /src/Loader/IExplorerLoader.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Config; 2 | 3 | namespace UnityExplorer 4 | { 5 | public interface IExplorerLoader 6 | { 7 | string ExplorerFolderDestination { get; } 8 | string ExplorerFolderName { get; } 9 | string UnhollowedModulesFolder { get; } 10 | 11 | ConfigHandler ConfigHandler { get; } 12 | 13 | Action OnLogMessage { get; } 14 | Action OnLogWarning { get; } 15 | Action OnLogError { get; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Loader/MelonLoader/ExplorerMelonMod.cs: -------------------------------------------------------------------------------- 1 | #if ML 2 | using System; 3 | using System.IO; 4 | using MelonLoader; 5 | using UnityExplorer; 6 | using UnityExplorer.Config; 7 | using UnityExplorer.Loader.ML; 8 | 9 | #if CPP 10 | [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.IL2CPP)] 11 | #else 12 | [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] 13 | #endif 14 | 15 | [assembly: MelonInfo(typeof(ExplorerMelonMod), ExplorerCore.NAME, ExplorerCore.VERSION, ExplorerCore.AUTHOR)] 16 | [assembly: MelonGame(null, null)] 17 | [assembly: MelonColor(ConsoleColor.DarkCyan)] 18 | 19 | namespace UnityExplorer 20 | { 21 | public class ExplorerMelonMod : MelonMod, IExplorerLoader 22 | { 23 | public string ExplorerFolderName => ExplorerCore.DEFAULT_EXPLORER_FOLDER_NAME; 24 | public string ExplorerFolderDestination => MelonHandler.ModsDirectory; 25 | 26 | public string UnhollowedModulesFolder => Path.Combine( 27 | Path.GetDirectoryName(MelonHandler.ModsDirectory), 28 | Path.Combine("MelonLoader", "Il2CppAssemblies")); 29 | 30 | public ConfigHandler ConfigHandler => _configHandler; 31 | public MelonLoaderConfigHandler _configHandler; 32 | 33 | public Action OnLogMessage => LoggerInstance.Msg; 34 | public Action OnLogWarning => LoggerInstance.Warning; 35 | public Action OnLogError => LoggerInstance.Error; 36 | 37 | public override void OnApplicationStart() 38 | { 39 | _configHandler = new MelonLoaderConfigHandler(); 40 | ExplorerCore.Init(this); 41 | } 42 | } 43 | } 44 | #endif -------------------------------------------------------------------------------- /src/Loader/MelonLoader/MelonLoaderConfigHandler.cs: -------------------------------------------------------------------------------- 1 | #if ML 2 | using MelonLoader; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using UnityEngine; 8 | using UnityExplorer.Config; 9 | 10 | namespace UnityExplorer.Loader.ML 11 | { 12 | public class MelonLoaderConfigHandler : ConfigHandler 13 | { 14 | internal const string CTG_NAME = "UnityExplorer"; 15 | 16 | internal MelonPreferences_Category prefCategory; 17 | 18 | public override void Init() 19 | { 20 | prefCategory = MelonPreferences.CreateCategory(CTG_NAME, $"{CTG_NAME} Settings", false, true); 21 | } 22 | 23 | public override void LoadConfig() 24 | { 25 | foreach (var entry in ConfigManager.ConfigElements) 26 | { 27 | var key = entry.Key; 28 | if (prefCategory.GetEntry(key) is MelonPreferences_Entry) 29 | { 30 | var config = entry.Value; 31 | config.BoxedValue = config.GetLoaderConfigValue(); 32 | } 33 | } 34 | } 35 | 36 | // This wrapper exists to handle the "LemonAction" delegates which ML now uses in 0.4.4+. 37 | // Reflection is required since the delegate type changed between 0.4.3 and 0.4.4. 38 | // A wrapper class is required to link the MelonPreferences_Entry and the delegate instance. 39 | public class EntryDelegateWrapper 40 | { 41 | public MelonPreferences_Entry entry; 42 | public ConfigElement config; 43 | 44 | public EntryDelegateWrapper(MelonPreferences_Entry entry, ConfigElement config) 45 | { 46 | this.entry = entry; 47 | this.config = config; 48 | var evt = entry.GetType().GetEvent("OnValueChangedUntyped"); 49 | evt.AddEventHandler(entry, Delegate.CreateDelegate(evt.EventHandlerType, this, GetType().GetMethod("OnChanged"))); 50 | } 51 | 52 | public void OnChanged() 53 | { 54 | if ((entry.Value == null && config.Value == null) || config.Value.Equals(entry.Value)) 55 | return; 56 | config.Value = entry.Value; 57 | } 58 | } 59 | 60 | public override void RegisterConfigElement(ConfigElement config) 61 | { 62 | var entry = prefCategory.CreateEntry(config.Name, config.Value, null, config.Description, config.IsInternal, false); 63 | new EntryDelegateWrapper(entry, config); 64 | } 65 | 66 | public override void SetConfigValue(ConfigElement config, T value) 67 | { 68 | if (prefCategory.GetEntry(config.Name) is MelonPreferences_Entry entry) 69 | { 70 | entry.Value = value; 71 | //entry.Save(); 72 | } 73 | } 74 | 75 | public override T GetConfigValue(ConfigElement config) 76 | { 77 | if (prefCategory.GetEntry(config.Name) is MelonPreferences_Entry entry) 78 | return entry.Value; 79 | 80 | return default; 81 | } 82 | 83 | public override void OnAnyConfigChanged() 84 | { 85 | } 86 | 87 | public override void SaveConfig() 88 | { 89 | MelonPreferences.Save(); 90 | } 91 | } 92 | } 93 | #endif -------------------------------------------------------------------------------- /src/Loader/Standalone/Editor/ExplorerEditorBehaviour.cs: -------------------------------------------------------------------------------- 1 | #if STANDALONE 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using System.Text; 8 | using UnityEngine; 9 | using UnityExplorer.Config; 10 | using UnityExplorer.UI; 11 | using UniverseLib; 12 | 13 | namespace UnityExplorer.Loader.Standalone 14 | { 15 | public class ExplorerEditorBehaviour : MonoBehaviour 16 | { 17 | internal static ExplorerEditorBehaviour Instance { get; private set; } 18 | 19 | public bool Hide_On_Startup = true; 20 | public KeyCode Master_Toggle_Key = KeyCode.F7; 21 | public UIManager.VerticalAnchor Main_Navbar_Anchor = UIManager.VerticalAnchor.Top; 22 | public bool Log_Unity_Debug = false; 23 | public float Startup_Delay_Time = 1f; 24 | public KeyCode World_MouseInspect_Keybind; 25 | public KeyCode UI_MouseInspect_Keybind; 26 | public bool Force_Unlock_Mouse = true; 27 | public KeyCode Force_Unlock_Toggle; 28 | public bool Disable_EventSystem_Override; 29 | 30 | internal void Awake() 31 | { 32 | Instance = this; 33 | 34 | ExplorerEditorLoader.Initialize(); 35 | DontDestroyOnLoad(this); 36 | this.gameObject.hideFlags = HideFlags.HideAndDontSave; 37 | } 38 | 39 | internal void OnApplicationQuit() 40 | { 41 | Destroy(this.gameObject); 42 | } 43 | 44 | internal void LoadConfigs() 45 | { 46 | ConfigManager.Hide_On_Startup.Value = this.Hide_On_Startup; 47 | ConfigManager.Master_Toggle.Value = this.Master_Toggle_Key; 48 | ConfigManager.Main_Navbar_Anchor.Value = this.Main_Navbar_Anchor; 49 | ConfigManager.Log_Unity_Debug.Value = this.Log_Unity_Debug; 50 | ConfigManager.Startup_Delay_Time.Value = this.Startup_Delay_Time; 51 | ConfigManager.World_MouseInspect_Keybind.Value = this.World_MouseInspect_Keybind; 52 | ConfigManager.UI_MouseInspect_Keybind.Value = this.UI_MouseInspect_Keybind; 53 | ConfigManager.Force_Unlock_Mouse.Value = this.Force_Unlock_Mouse; 54 | ConfigManager.Force_Unlock_Toggle.Value = this.Force_Unlock_Toggle; 55 | ConfigManager.Disable_EventSystem_Override.Value = this.Disable_EventSystem_Override; 56 | } 57 | } 58 | } 59 | #endif -------------------------------------------------------------------------------- /src/Loader/Standalone/Editor/ExplorerEditorLoader.cs: -------------------------------------------------------------------------------- 1 | #if STANDALONE 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text; 7 | using UnityEngine; 8 | 9 | namespace UnityExplorer.Loader.Standalone 10 | { 11 | public class ExplorerEditorLoader : ExplorerStandalone 12 | { 13 | public new string ExplorerFolderName => $"{ExplorerCore.DEFAULT_EXPLORER_FOLDER_NAME}~"; 14 | 15 | public static void Initialize() 16 | { 17 | Instance = new ExplorerEditorLoader(); 18 | OnLog += LogHandler; 19 | Instance.configHandler = new StandaloneConfigHandler(); 20 | 21 | ExplorerCore.Init(Instance); 22 | } 23 | 24 | static void LogHandler(string message, LogType logType) 25 | { 26 | switch (logType) 27 | { 28 | case LogType.Assert: Debug.LogError(message); break; 29 | case LogType.Error: Debug.LogError(message); break; 30 | case LogType.Exception: Debug.LogError(message); break; 31 | case LogType.Log: Debug.Log(message); break; 32 | case LogType.Warning: Debug.LogWarning(message); break; 33 | } 34 | } 35 | 36 | protected override void CheckExplorerFolder() 37 | { 38 | if (explorerFolderDest == null) 39 | explorerFolderDest = Path.GetDirectoryName(Application.dataPath); 40 | } 41 | } 42 | } 43 | #endif -------------------------------------------------------------------------------- /src/Loader/Standalone/ExplorerStandalone.cs: -------------------------------------------------------------------------------- 1 | #if STANDALONE 2 | using HarmonyLib; 3 | using System; 4 | using System.IO; 5 | using System.Reflection; 6 | using UnityEngine; 7 | using UnityExplorer.Config; 8 | using UnityEngine.EventSystems; 9 | using UniverseLib.Input; 10 | using UnityExplorer.Loader.Standalone; 11 | #if CPP 12 | using UnhollowerRuntimeLib; 13 | #endif 14 | 15 | namespace UnityExplorer 16 | { 17 | public class ExplorerStandalone : IExplorerLoader 18 | { 19 | public static ExplorerStandalone Instance { get; protected set; } 20 | 21 | /// 22 | /// Invoked whenever Explorer logs something. Subscribe to this to handle logging. 23 | /// 24 | public static event Action OnLog; 25 | 26 | public string UnhollowedModulesFolder => unhollowedPath; 27 | private string unhollowedPath; 28 | 29 | public ConfigHandler ConfigHandler => configHandler; 30 | internal StandaloneConfigHandler configHandler; 31 | 32 | public string ExplorerFolderName => ExplorerCore.DEFAULT_EXPLORER_FOLDER_NAME; 33 | public string ExplorerFolderDestination 34 | { 35 | get 36 | { 37 | CheckExplorerFolder(); 38 | return explorerFolderDest; 39 | } 40 | } 41 | protected static string explorerFolderDest; 42 | 43 | Action IExplorerLoader.OnLogMessage => (object log) => { OnLog?.Invoke(log?.ToString() ?? "", LogType.Log); }; 44 | Action IExplorerLoader.OnLogWarning => (object log) => { OnLog?.Invoke(log?.ToString() ?? "", LogType.Warning); }; 45 | Action IExplorerLoader.OnLogError => (object log) => { OnLog?.Invoke(log?.ToString() ?? "", LogType.Error); }; 46 | 47 | /// 48 | /// Call this to initialize UnityExplorer without adding a log listener or Unhollowed modules path. 49 | /// The default Unhollowed path "sinai-dev-UnityExplorer\Modules\" will be used. 50 | /// 51 | /// The new (or active, if one exists) instance of ExplorerStandalone. 52 | public static ExplorerStandalone CreateInstance() => CreateInstance(null, null); 53 | 54 | /// 55 | /// Call this to initialize UnityExplorer and add a listener for UnityExplorer's log messages, without specifying an Unhollowed modules path. 56 | /// The default Unhollowed path "sinai-dev-UnityExplorer\Modules\" will be used. 57 | /// 58 | /// Your log listener to handle UnityExplorer logs. 59 | /// The new (or active, if one exists) instance of ExplorerStandalone. 60 | public static ExplorerStandalone CreateInstance(Action logListener) => CreateInstance(logListener, null); 61 | 62 | /// 63 | /// Call this to initialize UnityExplorer with the provided log listener and Unhollowed modules path. 64 | /// 65 | /// Your log listener to handle UnityExplorer logs. 66 | /// The path of the Unhollowed modules, either relative or absolute. 67 | /// The new (or active, if one exists) instance of ExplorerStandalone. 68 | public static ExplorerStandalone CreateInstance(Action logListener, string unhollowedModulesPath) 69 | { 70 | if (Instance != null) 71 | return Instance; 72 | 73 | var instance = new ExplorerStandalone(); 74 | instance.Init(); 75 | instance.CheckExplorerFolder(); 76 | 77 | if (logListener != null) 78 | OnLog += logListener; 79 | 80 | if (string.IsNullOrEmpty(unhollowedModulesPath) || !Directory.Exists(unhollowedModulesPath)) 81 | instance.unhollowedPath = Path.Combine(ExplorerCore.ExplorerFolder, "Modules"); 82 | else 83 | instance.unhollowedPath = unhollowedModulesPath; 84 | 85 | return instance; 86 | } 87 | 88 | internal void Init() 89 | { 90 | Instance = this; 91 | configHandler = new StandaloneConfigHandler(); 92 | 93 | ExplorerCore.Init(this); 94 | } 95 | 96 | protected virtual void CheckExplorerFolder() 97 | { 98 | if (explorerFolderDest == null) 99 | { 100 | string assemblyLocation = Uri.UnescapeDataString(new Uri(typeof(ExplorerCore).Assembly.CodeBase).AbsolutePath); 101 | explorerFolderDest = Path.GetDirectoryName(assemblyLocation); 102 | } 103 | } 104 | } 105 | } 106 | #endif -------------------------------------------------------------------------------- /src/Loader/Standalone/StandaloneConfigHandler.cs: -------------------------------------------------------------------------------- 1 | #if STANDALONE 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using UnityExplorer.Config; 7 | using System.IO; 8 | using UnityEngine; 9 | using Tomlet; 10 | using Tomlet.Models; 11 | 12 | namespace UnityExplorer.Loader.Standalone 13 | { 14 | public class StandaloneConfigHandler : ConfigHandler 15 | { 16 | internal static string CONFIG_PATH; 17 | 18 | public override void Init() 19 | { 20 | CONFIG_PATH = Path.Combine(ExplorerCore.ExplorerFolder, "config.cfg"); 21 | } 22 | 23 | public override void LoadConfig() 24 | { 25 | if (!TryLoadConfig()) 26 | SaveConfig(); 27 | } 28 | 29 | public override void RegisterConfigElement(ConfigElement element) 30 | { 31 | // Not necessary 32 | } 33 | 34 | public override void SetConfigValue(ConfigElement element, T value) 35 | { 36 | // Not necessary, just save. 37 | SaveConfig(); 38 | } 39 | 40 | public override T GetConfigValue(ConfigElement element) 41 | { 42 | // Not necessary, just return the value. 43 | return element.Value; 44 | } 45 | 46 | public bool TryLoadConfig() 47 | { 48 | try 49 | { 50 | if (!File.Exists(CONFIG_PATH)) 51 | return false; 52 | 53 | var document = TomlParser.ParseFile(CONFIG_PATH); 54 | foreach (var key in document.Keys) 55 | { 56 | var config = ConfigManager.ConfigElements[key]; 57 | config.BoxedValue = StringToConfigValue(document.GetValue(key).StringValue, config.ElementType); 58 | } 59 | 60 | return true; 61 | } 62 | catch 63 | { 64 | return false; 65 | } 66 | } 67 | 68 | public object StringToConfigValue(string value, Type elementType) 69 | { 70 | if (elementType == typeof(KeyCode)) 71 | return (KeyCode)Enum.Parse(typeof(KeyCode), value); 72 | else if (elementType == typeof(bool)) 73 | return bool.Parse(value); 74 | else if (elementType == typeof(int)) 75 | return int.Parse(value); 76 | else if (elementType == typeof(float)) 77 | return float.Parse(value); 78 | else if (elementType.IsEnum) 79 | return Enum.Parse(elementType, value); 80 | else 81 | return value; 82 | } 83 | 84 | public override void OnAnyConfigChanged() 85 | { 86 | SaveConfig(); 87 | } 88 | 89 | public override void SaveConfig() 90 | { 91 | var document = TomlDocument.CreateEmpty(); 92 | foreach (var config in ConfigManager.ConfigElements) 93 | document.Put(config.Key, config.Value.BoxedValue.ToString()); 94 | 95 | if (!Directory.Exists(ExplorerCore.ExplorerFolder)) 96 | Directory.CreateDirectory(ExplorerCore.ExplorerFolder); 97 | 98 | File.WriteAllText(CONFIG_PATH, document.SerializedValue); 99 | } 100 | } 101 | } 102 | 103 | #endif -------------------------------------------------------------------------------- /src/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.InteropServices; 2 | using UnityExplorer; 3 | 4 | // General Information about an assembly is controlled through the following 5 | // set of attributes. Change these attribute values to modify the information 6 | // associated with an assembly. 7 | [assembly: AssemblyTitle(ExplorerCore.NAME)] 8 | [assembly: AssemblyDescription("")] 9 | [assembly: AssemblyConfiguration("")] 10 | [assembly: AssemblyCompany(ExplorerCore.AUTHOR)] 11 | [assembly: AssemblyProduct(ExplorerCore.NAME)] 12 | [assembly: AssemblyCopyright("")] 13 | [assembly: AssemblyTrademark("")] 14 | [assembly: AssemblyCulture("")] 15 | 16 | // Setting ComVisible to false makes the types in this assembly not visible 17 | // to COM components. If you need to access a type in this assembly from 18 | // COM, set the ComVisible attribute to true on that type. 19 | [assembly: ComVisible(false)] 20 | 21 | // The following GUID is for the ID of the typelib if this project is exposed to COM 22 | [assembly: Guid("b21dbde3-5d6f-4726-93ab-cc3cc68bae7d")] 23 | 24 | // Version information for an assembly consists of the following four values: 25 | // 26 | // Major Version 27 | // Minor Version 28 | // Build Number 29 | // Revision 30 | // 31 | // You can specify all the values or you can default the Build and Revision Numbers 32 | // by using the '*' as shown below: 33 | // [assembly: AssemblyVersion("1.0.*")] 34 | [assembly: AssemblyVersion(ExplorerCore.VERSION)] 35 | [assembly: AssemblyFileVersion(ExplorerCore.VERSION)] 36 | -------------------------------------------------------------------------------- /src/Runtime/MonoHelper.cs: -------------------------------------------------------------------------------- 1 | #if MONO 2 | 3 | namespace UnityExplorer.Runtime 4 | { 5 | public class MonoHelper : UERuntimeHelper 6 | { 7 | public override void SetupEvents() 8 | { 9 | Application.logMessageReceived += Application_logMessageReceived; 10 | } 11 | 12 | private void Application_logMessageReceived(string condition, string stackTrace, LogType type) 13 | => ExplorerCore.LogUnity(condition, type); 14 | } 15 | } 16 | 17 | #endif -------------------------------------------------------------------------------- /src/Runtime/UERuntimeHelper.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Config; 2 | 3 | namespace UnityExplorer.Runtime 4 | { 5 | // Not really that necessary anymore, can eventually just be refactored away into the few classes that use this class. 6 | 7 | public abstract class UERuntimeHelper 8 | { 9 | public static UERuntimeHelper Instance; 10 | 11 | public static void Init() 12 | { 13 | #if CPP 14 | Instance = new Il2CppHelper(); 15 | #else 16 | Instance = new MonoHelper(); 17 | #endif 18 | Instance.SetupEvents(); 19 | 20 | LoadBlacklistString(ConfigManager.Reflection_Signature_Blacklist.Value); 21 | ConfigManager.Reflection_Signature_Blacklist.OnValueChanged += (string val) => 22 | { 23 | LoadBlacklistString(val); 24 | }; 25 | } 26 | 27 | public abstract void SetupEvents(); 28 | 29 | private static readonly HashSet currentBlacklist = new(); 30 | 31 | public virtual string[] DefaultReflectionBlacklist => new string[0]; 32 | 33 | public static void LoadBlacklistString(string blacklist) 34 | { 35 | try 36 | { 37 | if (string.IsNullOrEmpty(blacklist) && !Instance.DefaultReflectionBlacklist.Any()) 38 | return; 39 | 40 | try 41 | { 42 | string[] sigs = blacklist.Split(';'); 43 | foreach (string sig in sigs) 44 | { 45 | string s = sig.Trim(); 46 | if (string.IsNullOrEmpty(s)) 47 | continue; 48 | if (!currentBlacklist.Contains(s)) 49 | currentBlacklist.Add(s); 50 | } 51 | } 52 | catch (Exception ex) 53 | { 54 | ExplorerCore.LogWarning($"Exception parsing blacklist string: {ex.ReflectionExToString()}"); 55 | } 56 | 57 | foreach (string sig in Instance.DefaultReflectionBlacklist) 58 | { 59 | if (!currentBlacklist.Contains(sig)) 60 | currentBlacklist.Add(sig); 61 | } 62 | 63 | Mono.CSharp.IL2CPP.Blacklist.SignatureBlacklist = currentBlacklist; 64 | } 65 | catch (Exception ex) 66 | { 67 | ExplorerCore.LogWarning($"Exception setting up reflection blacklist: {ex.ReflectionExToString()}"); 68 | } 69 | } 70 | 71 | public static bool IsBlacklisted(MemberInfo member) 72 | { 73 | if (ConfigManager.Reflection_Hide_NativeInfoPtrs.Value) { 74 | bool isNativeInfoPtr = member.Name.StartsWith("NativeFieldInfoPtr_") || member.Name.StartsWith("NativeMethodInfoPtr_"); 75 | if (isNativeInfoPtr) 76 | return true; 77 | } 78 | 79 | if (string.IsNullOrEmpty(member.DeclaringType?.Namespace)) 80 | return false; 81 | 82 | string sig = $"{member.DeclaringType.FullName}.{member.Name}"; 83 | 84 | return currentBlacklist.Contains(sig); 85 | } 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/Runtime/UnityCrashPrevention.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.Runtime 2 | { 3 | internal static class UnityCrashPrevention 4 | { 5 | static readonly HarmonyLib.Harmony harmony = new($"{ExplorerCore.GUID}.crashprevention"); 6 | 7 | internal static void Init() 8 | { 9 | TryPatch("get_renderingDisplaySize", nameof(Canvas_renderingDisplaySize_Prefix)); 10 | 11 | IEnumerable patched = harmony.GetPatchedMethods(); 12 | if (patched.Any()) 13 | ExplorerCore.Log( 14 | $"Initialized UnityCrashPrevention for: {string.Join(", ", patched.Select(it => $"{it.DeclaringType.Name}.{it.Name}").ToArray())}"); 15 | } 16 | 17 | internal static void TryPatch(string orig, string prefix, Type[] argTypes = null) 18 | { 19 | try 20 | { 21 | harmony.Patch( 22 | HarmonyLib.AccessTools.Method(typeof(T), orig, argTypes), 23 | new HarmonyLib.HarmonyMethod(HarmonyLib.AccessTools.Method(typeof(UnityCrashPrevention), prefix))); 24 | } 25 | catch //(Exception ex) 26 | { 27 | //ExplorerCore.Log($"Exception patching {typeof(T).Name}.{orig}: {ex}"); 28 | } 29 | } 30 | 31 | // In Unity 2020 they introduced "Canvas.renderingDisplaySize". 32 | // If you try to get the value on a Canvas which has a renderMode value of WorldSpace and no worldCamera set, 33 | // the game will Crash (I think from Unity trying to read from null ptr). 34 | internal static void Canvas_renderingDisplaySize_Prefix(Canvas __instance) 35 | { 36 | if (__instance.renderMode == RenderMode.WorldSpace && !__instance.worldCamera) 37 | throw new InvalidOperationException("Canvas is set to RenderMode.WorldSpace but not worldCamera is set."); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/UI/DisplayManager.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityExplorer.Config; 3 | using UniverseLib.Input; 4 | 5 | namespace UnityExplorer.UI 6 | { 7 | public static class DisplayManager 8 | { 9 | public static int ActiveDisplayIndex { get; private set; } 10 | public static Display ActiveDisplay => Display.displays[ActiveDisplayIndex]; 11 | 12 | public static int Width => ActiveDisplay.renderingWidth; 13 | public static int Height => ActiveDisplay.renderingHeight; 14 | 15 | public static Vector3 MousePosition => Application.isEditor 16 | ? InputManager.MousePosition 17 | : Display.RelativeMouseAt(InputManager.MousePosition); 18 | 19 | public static bool MouseInTargetDisplay => MousePosition.z == ActiveDisplayIndex; 20 | 21 | private static Camera canvasCamera; 22 | 23 | internal static void Init() 24 | { 25 | SetDisplay(ConfigManager.Target_Display.Value); 26 | ConfigManager.Target_Display.OnValueChanged += SetDisplay; 27 | } 28 | 29 | public static void SetDisplay(int display) 30 | { 31 | if (ActiveDisplayIndex == display) 32 | return; 33 | 34 | if (Display.displays.Length <= display) 35 | { 36 | ExplorerCore.LogWarning($"Cannot set display index to {display} as there are not enough monitors connected!"); 37 | 38 | if (ConfigManager.Target_Display.Value == display) 39 | ConfigManager.Target_Display.Value = 0; 40 | 41 | return; 42 | } 43 | 44 | ActiveDisplayIndex = display; 45 | ActiveDisplay.Activate(); 46 | 47 | UIManager.UICanvas.targetDisplay = display; 48 | 49 | // ensure a camera is targeting the display 50 | if (!Camera.main || Camera.main.targetDisplay != display) 51 | { 52 | if (!canvasCamera) 53 | { 54 | canvasCamera = new GameObject("UnityExplorer_CanvasCamera").AddComponent(); 55 | GameObject.DontDestroyOnLoad(canvasCamera.gameObject); 56 | canvasCamera.hideFlags = HideFlags.HideAndDontSave; 57 | } 58 | canvasCamera.targetDisplay = display; 59 | } 60 | 61 | RuntimeHelper.StartCoroutine(FixPanels()); 62 | } 63 | 64 | private static IEnumerator FixPanels() 65 | { 66 | yield return null; 67 | yield return null; 68 | 69 | foreach (Panels.UEPanel panel in UIManager.UIPanels.Values) 70 | { 71 | panel.EnsureValidSize(); 72 | panel.EnsureValidPosition(); 73 | panel.Dragger.OnEndResize(); 74 | } 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/UI/ExplorerUIBase.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI; 2 | using UniverseLib.UI.Panels; 3 | 4 | namespace UnityExplorer.UI 5 | { 6 | internal class ExplorerUIBase : UIBase 7 | { 8 | public ExplorerUIBase(string id, Action updateMethod) : base(id, updateMethod) { } 9 | 10 | protected override PanelManager CreatePanelManager() 11 | { 12 | return new UEPanelManager(this); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/UI/Notification.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI; 2 | 3 | namespace UnityExplorer.UI 4 | { 5 | public static class Notification 6 | { 7 | private static Text popupLabel; 8 | 9 | private static string _currentNotification; 10 | private static float _timeOfLastNotification; 11 | 12 | public static void Init() 13 | { 14 | ConstructUI(); 15 | } 16 | 17 | public static void ShowMessage(string message) 18 | { 19 | popupLabel.text = message; 20 | _currentNotification = message; 21 | _timeOfLastNotification = Time.realtimeSinceStartup; 22 | 23 | popupLabel.transform.localPosition = UIManager.UIRootRect.InverseTransformPoint(DisplayManager.MousePosition) + (Vector3.up * 25); 24 | } 25 | 26 | public static void Update() 27 | { 28 | if (_currentNotification != null) 29 | { 30 | if (Time.realtimeSinceStartup - _timeOfLastNotification > 2f) 31 | { 32 | _currentNotification = null; 33 | popupLabel.text = ""; 34 | } 35 | } 36 | } 37 | 38 | private static void ConstructUI() 39 | { 40 | popupLabel = UIFactory.CreateLabel(UIManager.UIRoot, "ClipboardNotification", "", TextAnchor.MiddleCenter); 41 | popupLabel.rectTransform.sizeDelta = new(500, 100); 42 | popupLabel.gameObject.AddComponent(); 43 | CanvasGroup popupGroup = popupLabel.gameObject.AddComponent(); 44 | popupGroup.blocksRaycasts = false; 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/UI/Panels/ClipboardPanel.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI; 2 | 3 | namespace UnityExplorer.UI.Panels 4 | { 5 | public class ClipboardPanel : UEPanel 6 | { 7 | public static object Current { get; private set; } 8 | 9 | public override string Name => "Clipboard"; 10 | public override UIManager.Panels PanelType => UIManager.Panels.Clipboard; 11 | 12 | public override int MinWidth => 500; 13 | public override int MinHeight => 95; 14 | public override Vector2 DefaultAnchorMin => new(0.1f, 0.05f); 15 | public override Vector2 DefaultAnchorMax => new(0.4f, 0.15f); 16 | 17 | public override bool CanDragAndResize => true; 18 | public override bool NavButtonWanted => true; 19 | public override bool ShouldSaveActiveState => true; 20 | public override bool ShowByDefault => true; 21 | 22 | private static Text CurrentPasteLabel; 23 | 24 | public ClipboardPanel(UIBase owner) : base(owner) 25 | { 26 | } 27 | 28 | public static void Copy(object obj) 29 | { 30 | Current = obj; 31 | Notification.ShowMessage("Copied!"); 32 | UpdateCurrentPasteInfo(); 33 | } 34 | 35 | public static bool TryPaste(Type targetType, out object paste) 36 | { 37 | paste = Current; 38 | Type pasteType = Current?.GetActualType(); 39 | 40 | if (Current != null && !targetType.IsAssignableFrom(pasteType)) 41 | { 42 | Notification.ShowMessage($"Cannot assign '{pasteType.Name}' to '{targetType.Name}'!"); 43 | return false; 44 | } 45 | 46 | Notification.ShowMessage("Pasted!"); 47 | return true; 48 | } 49 | 50 | public static void ClearClipboard() 51 | { 52 | Current = null; 53 | UpdateCurrentPasteInfo(); 54 | } 55 | 56 | private static void UpdateCurrentPasteInfo() 57 | { 58 | CurrentPasteLabel.text = ToStringUtility.ToStringWithType(Current, typeof(object), false); 59 | } 60 | 61 | private static void InspectClipboard() 62 | { 63 | if (Current.IsNullOrDestroyed()) 64 | { 65 | Notification.ShowMessage("Cannot inspect a null or destroyed object!"); 66 | return; 67 | } 68 | 69 | InspectorManager.Inspect(Current); 70 | } 71 | 72 | public override void SetDefaultSizeAndPosition() 73 | { 74 | base.SetDefaultSizeAndPosition(); 75 | 76 | this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth); 77 | this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight); 78 | } 79 | 80 | protected override void ConstructPanelContent() 81 | { 82 | this.UIRoot.GetComponent().color = new(0.1f, 0.1f, 0.1f); 83 | 84 | // Actual panel content 85 | 86 | GameObject firstRow = UIFactory.CreateHorizontalGroup(ContentRoot, "FirstRow", false, false, true, true, 5, new(2, 2, 2, 2), new(1, 1, 1, 0)); 87 | UIFactory.SetLayoutElement(firstRow, minHeight: 25, flexibleWidth: 999); 88 | 89 | // Title for "Current Paste:" 90 | Text currentPasteTitle = UIFactory.CreateLabel(firstRow, "CurrentPasteTitle", "Current paste:", TextAnchor.MiddleLeft, color: Color.grey); 91 | UIFactory.SetLayoutElement(currentPasteTitle.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999); 92 | 93 | // Clear clipboard button 94 | UniverseLib.UI.Models.ButtonRef clearButton = UIFactory.CreateButton(firstRow, "ClearPasteButton", "Clear Clipboard"); 95 | UIFactory.SetLayoutElement(clearButton.Component.gameObject, minWidth: 120, minHeight: 25, flexibleWidth: 0); 96 | clearButton.OnClick += () => Copy(null); 97 | 98 | // Current Paste info row 99 | GameObject currentPasteHolder = UIFactory.CreateHorizontalGroup(ContentRoot, "SecondRow", false, false, true, true, 0, 100 | new(2, 2, 2, 2), childAlignment: TextAnchor.UpperCenter); 101 | 102 | // Actual current paste info label 103 | CurrentPasteLabel = UIFactory.CreateLabel(currentPasteHolder, "CurrentPasteInfo", "not set", TextAnchor.UpperLeft); 104 | UIFactory.SetLayoutElement(CurrentPasteLabel.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999, flexibleHeight: 999); 105 | UpdateCurrentPasteInfo(); 106 | 107 | // Inspect button 108 | UniverseLib.UI.Models.ButtonRef inspectButton = UIFactory.CreateButton(currentPasteHolder, "InspectButton", "Inspect"); 109 | UIFactory.SetLayoutElement(inspectButton.Component.gameObject, minHeight: 25, flexibleHeight: 0, minWidth: 80, flexibleWidth: 0); 110 | inspectButton.OnClick += InspectClipboard; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/UI/Panels/HookManagerPanel.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Hooks; 2 | using UnityExplorer.UI.Widgets; 3 | using UniverseLib.UI; 4 | 5 | namespace UnityExplorer.UI.Panels 6 | { 7 | public class HookManagerPanel : UEPanel 8 | { 9 | public static HookManagerPanel Instance { get; private set; } 10 | 11 | public enum Pages 12 | { 13 | ClassMethodSelector, 14 | HookSourceEditor, 15 | GenericArgsSelector, 16 | } 17 | 18 | public static HookCreator hookCreator; 19 | public static HookList hookList; 20 | public static GenericConstructorWidget genericArgsHandler; 21 | 22 | // Panel 23 | public override UIManager.Panels PanelType => UIManager.Panels.HookManager; 24 | public override string Name => "Hooks"; 25 | public override bool ShowByDefault => false; 26 | public override int MinWidth => 400; 27 | public override int MinHeight => 400; 28 | public override Vector2 DefaultAnchorMin => new(0.5f, 0.5f); 29 | public override Vector2 DefaultAnchorMax => new(0.5f, 0.5f); 30 | 31 | public Pages CurrentPage { get; private set; } = Pages.ClassMethodSelector; 32 | 33 | public HookManagerPanel(UIBase owner) : base(owner) 34 | { 35 | } 36 | 37 | public void SetPage(Pages page) 38 | { 39 | switch (page) 40 | { 41 | case Pages.ClassMethodSelector: 42 | HookCreator.AddHooksRoot.SetActive(true); 43 | HookCreator.EditorRoot.SetActive(false); 44 | genericArgsHandler.UIRoot.SetActive(false); 45 | break; 46 | 47 | case Pages.HookSourceEditor: 48 | HookCreator.AddHooksRoot.SetActive(false); 49 | HookCreator.EditorRoot.SetActive(true); 50 | genericArgsHandler.UIRoot.SetActive(false); 51 | break; 52 | 53 | case Pages.GenericArgsSelector: 54 | HookCreator.AddHooksRoot.SetActive(false); 55 | HookCreator.EditorRoot.SetActive(false); 56 | genericArgsHandler.UIRoot.SetActive(true); 57 | break; 58 | } 59 | } 60 | 61 | public override void SetDefaultSizeAndPosition() 62 | { 63 | base.SetDefaultSizeAndPosition(); 64 | 65 | this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, MinWidth); 66 | this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, MinHeight); 67 | } 68 | 69 | protected override void ConstructPanelContent() 70 | { 71 | Instance = this; 72 | hookList = new(); 73 | hookCreator = new(); 74 | genericArgsHandler = new(); 75 | 76 | UIFactory.SetLayoutGroup(ContentRoot, true, false); 77 | 78 | // GameObject baseHoriGroup = UIFactory.CreateHorizontalGroup(ContentRoot, "HoriGroup", true, true, true, true); 79 | // UIFactory.SetLayoutElement(baseHoriGroup, flexibleWidth: 9999, flexibleHeight: 9999); 80 | 81 | // // Left Group 82 | 83 | //GameObject leftGroup = UIFactory.CreateVerticalGroup(ContentRoot, "LeftGroup", true, true, true, true); 84 | UIFactory.SetLayoutElement(ContentRoot.gameObject, minWidth: 300, flexibleWidth: 9999, flexibleHeight: 9999); 85 | 86 | hookList.ConstructUI(ContentRoot); 87 | 88 | // // Right Group 89 | 90 | //GameObject rightGroup = UIFactory.CreateVerticalGroup(ContentRoot, "RightGroup", true, true, true, true); 91 | UIFactory.SetLayoutElement(ContentRoot, minWidth: 300, flexibleWidth: 9999, flexibleHeight: 9999); 92 | 93 | hookCreator.ConstructAddHooksView(ContentRoot); 94 | 95 | hookCreator.ConstructEditor(ContentRoot); 96 | HookCreator.EditorRoot.SetActive(false); 97 | 98 | genericArgsHandler.ConstructUI(ContentRoot); 99 | genericArgsHandler.UIRoot.SetActive(false); 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/UI/Panels/InspectorPanel.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Inspectors; 2 | using UniverseLib.UI; 3 | 4 | namespace UnityExplorer.UI.Panels 5 | { 6 | public class InspectorPanel : UEPanel 7 | { 8 | public static InspectorPanel Instance { get; private set; } 9 | 10 | public override string Name => "Inspector"; 11 | public override UIManager.Panels PanelType => UIManager.Panels.Inspector; 12 | public override bool ShouldSaveActiveState => false; 13 | 14 | public override int MinWidth => 810; 15 | public override int MinHeight => 350; 16 | public override Vector2 DefaultAnchorMin => new(0.35f, 0.175f); 17 | public override Vector2 DefaultAnchorMax => new(0.8f, 0.925f); 18 | 19 | public GameObject NavbarHolder; 20 | public Dropdown MouseInspectDropdown; 21 | public GameObject ContentHolder; 22 | public RectTransform ContentRect; 23 | 24 | public static float CurrentPanelWidth => Instance.Rect.rect.width; 25 | public static float CurrentPanelHeight => Instance.Rect.rect.height; 26 | 27 | public InspectorPanel(UIBase owner) : base(owner) 28 | { 29 | Instance = this; 30 | } 31 | 32 | public override void Update() 33 | { 34 | InspectorManager.Update(); 35 | } 36 | 37 | public override void OnFinishResize() 38 | { 39 | base.OnFinishResize(); 40 | 41 | InspectorManager.PanelWidth = this.Rect.rect.width; 42 | InspectorManager.OnPanelResized(Rect.rect.width); 43 | } 44 | 45 | protected override void ConstructPanelContent() 46 | { 47 | GameObject closeHolder = this.TitleBar.transform.Find("CloseHolder").gameObject; 48 | 49 | // Inspect under mouse dropdown on title bar 50 | 51 | GameObject mouseDropdown = UIFactory.CreateDropdown(closeHolder, "MouseInspectDropdown", out MouseInspectDropdown, "Mouse Inspect", 14, 52 | MouseInspector.OnDropdownSelect); 53 | UIFactory.SetLayoutElement(mouseDropdown, minHeight: 25, minWidth: 140); 54 | MouseInspectDropdown.options.Add(new Dropdown.OptionData("Mouse Inspect")); 55 | MouseInspectDropdown.options.Add(new Dropdown.OptionData("World")); 56 | MouseInspectDropdown.options.Add(new Dropdown.OptionData("UI")); 57 | mouseDropdown.transform.SetSiblingIndex(0); 58 | 59 | // add close all button to titlebar 60 | 61 | UniverseLib.UI.Models.ButtonRef closeAllBtn = UIFactory.CreateButton(closeHolder.gameObject, "CloseAllBtn", "Close All", 62 | new Color(0.3f, 0.2f, 0.2f)); 63 | UIFactory.SetLayoutElement(closeAllBtn.Component.gameObject, minHeight: 25, minWidth: 80); 64 | closeAllBtn.Component.transform.SetSiblingIndex(closeAllBtn.Component.transform.GetSiblingIndex() - 1); 65 | closeAllBtn.OnClick += InspectorManager.CloseAllTabs; 66 | 67 | // this.UIRoot.GetComponent().enabled = false; 68 | 69 | UIFactory.SetLayoutGroup(this.ContentRoot, true, true, true, true, 4, padLeft: 5, padRight: 5); 70 | 71 | this.NavbarHolder = UIFactory.CreateGridGroup(this.ContentRoot, "Navbar", new Vector2(200, 22), new Vector2(4, 4), 72 | new Color(0.05f, 0.05f, 0.05f)); 73 | //UIFactory.SetLayoutElement(NavbarHolder, flexibleWidth: 9999, minHeight: 0, preferredHeight: 0, flexibleHeight: 9999); 74 | NavbarHolder.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; 75 | 76 | this.ContentHolder = UIFactory.CreateVerticalGroup(this.ContentRoot, "ContentHolder", true, true, true, true, 0, default, 77 | new Color(0.1f, 0.1f, 0.1f)); 78 | UIFactory.SetLayoutElement(ContentHolder, flexibleHeight: 9999); 79 | ContentRect = ContentHolder.GetComponent(); 80 | 81 | this.SetActive(false); 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /src/UI/Panels/MouseInspectorResultsPanel.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Inspectors.MouseInspectors; 2 | using UniverseLib.UI; 3 | using UniverseLib.UI.Widgets.ButtonList; 4 | using UniverseLib.UI.Widgets.ScrollView; 5 | 6 | namespace UnityExplorer.UI.Panels 7 | { 8 | public class MouseInspectorResultsPanel : UEPanel 9 | { 10 | public override UIManager.Panels PanelType => UIManager.Panels.UIInspectorResults; 11 | 12 | public override string Name => "UI Inspector Results"; 13 | 14 | public override int MinWidth => 500; 15 | public override int MinHeight => 500; 16 | public override Vector2 DefaultAnchorMin => new(0.5f, 0.5f); 17 | public override Vector2 DefaultAnchorMax => new(0.5f, 0.5f); 18 | 19 | public override bool CanDragAndResize => true; 20 | public override bool NavButtonWanted => false; 21 | public override bool ShouldSaveActiveState => false; 22 | public override bool ShowByDefault => false; 23 | 24 | private ButtonListHandler dataHandler; 25 | private ScrollPool buttonScrollPool; 26 | 27 | public MouseInspectorResultsPanel(UIBase owner) : base(owner) 28 | { 29 | } 30 | 31 | public void ShowResults() 32 | { 33 | dataHandler.RefreshData(); 34 | buttonScrollPool.Refresh(true, true); 35 | } 36 | 37 | private List GetEntries() => UiInspector.LastHitObjects; 38 | 39 | private bool ShouldDisplayCell(object cell, string filter) => true; 40 | 41 | private void OnCellClicked(int index) 42 | { 43 | if (index >= UiInspector.LastHitObjects.Count) 44 | return; 45 | 46 | InspectorManager.Inspect(UiInspector.LastHitObjects[index]); 47 | } 48 | 49 | private void SetCell(ButtonCell cell, int index) 50 | { 51 | if (index >= UiInspector.LastHitObjects.Count) 52 | return; 53 | 54 | GameObject obj = UiInspector.LastHitObjects[index]; 55 | cell.Button.ButtonText.text = $"{obj.name} ({obj.transform.GetTransformPath(true)})"; 56 | } 57 | 58 | public override void SetDefaultSizeAndPosition() 59 | { 60 | base.SetDefaultSizeAndPosition(); 61 | 62 | this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 500f); 63 | this.Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Vertical, 500f); 64 | } 65 | 66 | protected override void ConstructPanelContent() 67 | { 68 | dataHandler = new ButtonListHandler(buttonScrollPool, GetEntries, SetCell, ShouldDisplayCell, OnCellClicked); 69 | 70 | buttonScrollPool = UIFactory.CreateScrollPool(this.ContentRoot, "ResultsList", out GameObject scrollObj, 71 | out GameObject scrollContent); 72 | 73 | buttonScrollPool.Initialize(dataHandler); 74 | UIFactory.SetLayoutElement(scrollObj, flexibleHeight: 9999); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/UI/Panels/ObjectExplorerPanel.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.ObjectExplorer; 2 | using UniverseLib.UI; 3 | using UniverseLib.UI.Models; 4 | 5 | namespace UnityExplorer.UI.Panels 6 | { 7 | public class ObjectExplorerPanel : UEPanel 8 | { 9 | public override string Name => "Object Explorer"; 10 | public override UIManager.Panels PanelType => UIManager.Panels.ObjectExplorer; 11 | 12 | public override int MinWidth => 350; 13 | public override int MinHeight => 200; 14 | public override Vector2 DefaultAnchorMin => new(0.125f, 0.175f); 15 | public override Vector2 DefaultAnchorMax => new(0.325f, 0.925f); 16 | 17 | public SceneExplorer SceneExplorer; 18 | public ObjectSearch ObjectSearch; 19 | 20 | public override bool ShowByDefault => true; 21 | public override bool ShouldSaveActiveState => true; 22 | 23 | public int SelectedTab = 0; 24 | private readonly List tabPages = new(); 25 | private readonly List tabButtons = new(); 26 | 27 | public ObjectExplorerPanel(UIBase owner) : base(owner) 28 | { 29 | } 30 | 31 | public void SetTab(int tabIndex) 32 | { 33 | if (SelectedTab != -1) 34 | DisableTab(SelectedTab); 35 | 36 | UIModel content = tabPages[tabIndex]; 37 | content.SetActive(true); 38 | 39 | ButtonRef button = tabButtons[tabIndex]; 40 | RuntimeHelper.SetColorBlock(button.Component, UniversalUI.EnabledButtonColor, UniversalUI.EnabledButtonColor * 1.2f); 41 | 42 | SelectedTab = tabIndex; 43 | SaveInternalData(); 44 | } 45 | 46 | private void DisableTab(int tabIndex) 47 | { 48 | tabPages[tabIndex].SetActive(false); 49 | RuntimeHelper.SetColorBlock(tabButtons[tabIndex].Component, UniversalUI.DisabledButtonColor, UniversalUI.DisabledButtonColor * 1.2f); 50 | } 51 | 52 | public override void Update() 53 | { 54 | if (SelectedTab == 0) 55 | SceneExplorer.Update(); 56 | else 57 | ObjectSearch.Update(); 58 | } 59 | 60 | public override string ToSaveData() 61 | { 62 | return string.Join("|", new string[] { base.ToSaveData(), SelectedTab.ToString() }); 63 | } 64 | 65 | protected override void ApplySaveData(string data) 66 | { 67 | base.ApplySaveData(data); 68 | 69 | try 70 | { 71 | int tab = int.Parse(data.Split('|').Last()); 72 | SelectedTab = tab; 73 | } 74 | catch 75 | { 76 | SelectedTab = 0; 77 | } 78 | 79 | SelectedTab = Math.Max(0, SelectedTab); 80 | SelectedTab = Math.Min(1, SelectedTab); 81 | 82 | SetTab(SelectedTab); 83 | } 84 | 85 | protected override void ConstructPanelContent() 86 | { 87 | // Tab bar 88 | GameObject tabGroup = UIFactory.CreateHorizontalGroup(ContentRoot, "TabBar", true, true, true, true, 2, new Vector4(2, 2, 2, 2)); 89 | UIFactory.SetLayoutElement(tabGroup, minHeight: 25, flexibleHeight: 0); 90 | 91 | // Scene Explorer 92 | SceneExplorer = new SceneExplorer(this); 93 | SceneExplorer.ConstructUI(ContentRoot); 94 | tabPages.Add(SceneExplorer); 95 | 96 | // Object search 97 | ObjectSearch = new ObjectSearch(this); 98 | ObjectSearch.ConstructUI(ContentRoot); 99 | tabPages.Add(ObjectSearch); 100 | 101 | // set up tabs 102 | AddTabButton(tabGroup, "Scene Explorer"); 103 | AddTabButton(tabGroup, "Object Search"); 104 | 105 | // default active state: Active 106 | this.SetActive(true); 107 | } 108 | 109 | private void AddTabButton(GameObject tabGroup, string label) 110 | { 111 | ButtonRef button = UIFactory.CreateButton(tabGroup, $"Button_{label}", label); 112 | 113 | int idx = tabButtons.Count; 114 | //button.onClick.AddListener(() => { SetTab(idx); }); 115 | button.OnClick += () => { SetTab(idx); }; 116 | 117 | tabButtons.Add(button); 118 | 119 | DisableTab(tabButtons.Count - 1); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/UI/Panels/OptionsPanel.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.CacheObject; 2 | using UnityExplorer.CacheObject.Views; 3 | using UnityExplorer.Config; 4 | using UniverseLib.UI; 5 | using UniverseLib.UI.Widgets.ScrollView; 6 | 7 | namespace UnityExplorer.UI.Panels 8 | { 9 | public class OptionsPanel : UEPanel, ICacheObjectController, ICellPoolDataSource 10 | { 11 | public override string Name => "Options"; 12 | public override UIManager.Panels PanelType => UIManager.Panels.Options; 13 | 14 | public override int MinWidth => 600; 15 | public override int MinHeight => 200; 16 | public override Vector2 DefaultAnchorMin => new(0.5f, 0.1f); 17 | public override Vector2 DefaultAnchorMax => new(0.5f, 0.85f); 18 | 19 | public override bool ShouldSaveActiveState => false; 20 | public override bool ShowByDefault => false; 21 | 22 | // Entry holders 23 | private readonly List configEntries = new(); 24 | 25 | // ICacheObjectController 26 | public CacheObjectBase ParentCacheObject => null; 27 | public object Target => null; 28 | public Type TargetType => null; 29 | public bool CanWrite => true; 30 | 31 | // ICellPoolDataSource 32 | public int ItemCount => configEntries.Count; 33 | 34 | public OptionsPanel(UIBase owner) : base(owner) 35 | { 36 | foreach (KeyValuePair entry in ConfigManager.ConfigElements) 37 | { 38 | CacheConfigEntry cache = new(entry.Value) 39 | { 40 | Owner = this 41 | }; 42 | configEntries.Add(cache); 43 | } 44 | 45 | foreach (CacheConfigEntry config in configEntries) 46 | config.UpdateValueFromSource(); 47 | } 48 | 49 | public void OnCellBorrowed(ConfigEntryCell cell) 50 | { 51 | } 52 | 53 | public void SetCell(ConfigEntryCell cell, int index) 54 | { 55 | CacheObjectControllerHelper.SetCell(cell, index, this.configEntries, null); 56 | } 57 | 58 | // UI Construction 59 | 60 | public override void SetDefaultSizeAndPosition() 61 | { 62 | base.SetDefaultSizeAndPosition(); 63 | 64 | Rect.SetSizeWithCurrentAnchors(RectTransform.Axis.Horizontal, 600f); 65 | } 66 | 67 | protected override void ConstructPanelContent() 68 | { 69 | // Save button 70 | 71 | UniverseLib.UI.Models.ButtonRef saveBtn = UIFactory.CreateButton(this.ContentRoot, "Save", "Save Options", new Color(0.2f, 0.3f, 0.2f)); 72 | UIFactory.SetLayoutElement(saveBtn.Component.gameObject, flexibleWidth: 9999, minHeight: 30, flexibleHeight: 0); 73 | saveBtn.OnClick += ConfigManager.Handler.SaveConfig; 74 | 75 | // Config entries 76 | 77 | ScrollPool scrollPool = UIFactory.CreateScrollPool( 78 | this.ContentRoot, 79 | "ConfigEntries", 80 | out GameObject scrollObj, 81 | out GameObject scrollContent); 82 | 83 | scrollPool.Initialize(this); 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/UI/Panels/UEPanelDragger.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI.Panels; 2 | 3 | namespace UnityExplorer.UI.Panels 4 | { 5 | public class UEPanelDragger : PanelDragger 6 | { 7 | public UEPanelDragger(PanelBase uiPanel) : base(uiPanel) { } 8 | 9 | protected override bool MouseInResizeArea(Vector2 mousePos) 10 | { 11 | return !UIManager.NavBarRect.rect.Contains(UIManager.NavBarRect.InverseTransformPoint(mousePos)) 12 | && base.MouseInResizeArea(mousePos); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/UI/UEPanelManager.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.UI.Panels; 2 | using UniverseLib.UI; 3 | using UniverseLib.UI.Panels; 4 | 5 | namespace UnityExplorer.UI 6 | { 7 | public class UEPanelManager : PanelManager 8 | { 9 | public UEPanelManager(UIBase owner) : base(owner) { } 10 | 11 | protected override Vector3 MousePosition => DisplayManager.MousePosition; 12 | 13 | protected override Vector2 ScreenDimensions => new(DisplayManager.Width, DisplayManager.Height); 14 | 15 | protected override bool MouseInTargetDisplay => DisplayManager.MouseInTargetDisplay; 16 | 17 | internal void DoInvokeOnPanelsReordered() 18 | { 19 | InvokeOnPanelsReordered(); 20 | } 21 | 22 | protected override void SortDraggerHeirarchy() 23 | { 24 | base.SortDraggerHeirarchy(); 25 | 26 | // move AutoCompleter to first update 27 | if (!UIManager.Initializing && AutoCompleteModal.Instance != null) 28 | { 29 | this.draggerInstances.Remove(AutoCompleteModal.Instance.Dragger); 30 | this.draggerInstances.Insert(0, AutoCompleteModal.Instance.Dragger); 31 | } 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/UI/Widgets/AutoComplete/EnumCompleter.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Specialized; 2 | using UnityExplorer.CacheObject.IValues; 3 | using UnityExplorer.UI.Panels; 4 | using UniverseLib.UI.Models; 5 | 6 | namespace UnityExplorer.UI.Widgets.AutoComplete 7 | { 8 | public class EnumCompleter : ISuggestionProvider 9 | { 10 | public bool Enabled 11 | { 12 | get => _enabled; 13 | set 14 | { 15 | _enabled = value; 16 | if (!_enabled) 17 | AutoCompleteModal.Instance.ReleaseOwnership(this); 18 | } 19 | } 20 | private bool _enabled = true; 21 | 22 | public event Action SuggestionClicked; 23 | 24 | public Type EnumType { get; set; } 25 | 26 | public InputFieldRef InputField { get; } 27 | public bool AnchorToCaretPosition => false; 28 | 29 | private readonly List suggestions = new(); 30 | private readonly HashSet suggestedValues = new(); 31 | 32 | private OrderedDictionary enumValues; 33 | 34 | internal string chosenSuggestion; 35 | 36 | bool ISuggestionProvider.AllowNavigation => false; 37 | 38 | public EnumCompleter(Type enumType, InputFieldRef inputField) 39 | { 40 | EnumType = enumType; 41 | InputField = inputField; 42 | 43 | inputField.OnValueChanged += OnInputFieldChanged; 44 | 45 | if (EnumType != null) 46 | CacheEnumValues(); 47 | } 48 | 49 | public void CacheEnumValues() 50 | { 51 | enumValues = InteractiveEnum.GetEnumValues(EnumType); 52 | } 53 | 54 | private string GetLastSplitInput(string fullInput) 55 | { 56 | string ret = fullInput; 57 | 58 | int lastSplit = fullInput.LastIndexOf(','); 59 | if (lastSplit >= 0) 60 | { 61 | lastSplit++; 62 | if (lastSplit == fullInput.Length) 63 | ret = ""; 64 | else 65 | ret = fullInput.Substring(lastSplit); 66 | } 67 | 68 | return ret; 69 | } 70 | 71 | public void OnSuggestionClicked(Suggestion suggestion) 72 | { 73 | chosenSuggestion = suggestion.UnderlyingValue; 74 | 75 | string lastInput = GetLastSplitInput(InputField.Text); 76 | 77 | if (lastInput != suggestion.UnderlyingValue) 78 | { 79 | string valueToSet = InputField.Text; 80 | 81 | if (valueToSet.Length > 0) 82 | valueToSet = valueToSet.Substring(0, InputField.Text.Length - lastInput.Length); 83 | 84 | valueToSet += suggestion.UnderlyingValue; 85 | 86 | InputField.Text = valueToSet; 87 | 88 | //InputField.Text += suggestion.UnderlyingValue.Substring(lastInput.Length); 89 | } 90 | 91 | SuggestionClicked?.Invoke(suggestion); 92 | 93 | suggestions.Clear(); 94 | AutoCompleteModal.Instance.SetSuggestions(suggestions); 95 | } 96 | 97 | public void HelperButtonClicked() 98 | { 99 | GetSuggestions(""); 100 | AutoCompleteModal.TakeOwnership(this); 101 | AutoCompleteModal.Instance.SetSuggestions(suggestions); 102 | } 103 | 104 | private void OnInputFieldChanged(string value) 105 | { 106 | if (!Enabled) 107 | return; 108 | 109 | if (string.IsNullOrEmpty(value) || GetLastSplitInput(value) == chosenSuggestion) 110 | { 111 | chosenSuggestion = null; 112 | AutoCompleteModal.Instance.ReleaseOwnership(this); 113 | } 114 | else 115 | { 116 | GetSuggestions(value); 117 | 118 | AutoCompleteModal.TakeOwnership(this); 119 | AutoCompleteModal.Instance.SetSuggestions(suggestions); 120 | } 121 | } 122 | 123 | private void GetSuggestions(string value) 124 | { 125 | suggestions.Clear(); 126 | suggestedValues.Clear(); 127 | 128 | if (EnumType == null) 129 | { 130 | ExplorerCore.LogWarning("Autocompleter Base enum type is null!"); 131 | return; 132 | } 133 | 134 | value = GetLastSplitInput(value); 135 | 136 | for (int i = 0; i < this.enumValues.Count; i++) 137 | { 138 | CachedEnumValue enumValue = (CachedEnumValue)enumValues[i]; 139 | if (enumValue.Name.ContainsIgnoreCase(value)) 140 | AddSuggestion(enumValue.Name); 141 | } 142 | } 143 | 144 | internal static readonly Dictionary sharedValueToLabel = new(4096); 145 | 146 | void AddSuggestion(string value) 147 | { 148 | if (suggestedValues.Contains(value)) 149 | return; 150 | suggestedValues.Add(value); 151 | 152 | if (!sharedValueToLabel.ContainsKey(value)) 153 | sharedValueToLabel.Add(value, $"{value}"); 154 | 155 | suggestions.Add(new Suggestion(sharedValueToLabel[value], value)); 156 | } 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /src/UI/Widgets/AutoComplete/ISuggestionProvider.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI.Models; 2 | 3 | namespace UnityExplorer.UI.Widgets.AutoComplete 4 | { 5 | public interface ISuggestionProvider 6 | { 7 | InputFieldRef InputField { get; } 8 | bool AnchorToCaretPosition { get; } 9 | 10 | bool AllowNavigation { get; } 11 | 12 | void OnSuggestionClicked(Suggestion suggestion); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/UI/Widgets/AutoComplete/Suggestion.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.UI.Widgets.AutoComplete 2 | { 3 | public struct Suggestion 4 | { 5 | public readonly string DisplayText; 6 | public readonly string UnderlyingValue; 7 | 8 | public Suggestion(string displayText, string underlyingValue) 9 | { 10 | DisplayText = displayText; 11 | UnderlyingValue = underlyingValue; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/UI/Widgets/EvaluateWidget/BaseArgumentHandler.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.UI.Widgets.AutoComplete; 2 | using UniverseLib.UI; 3 | using UniverseLib.UI.Models; 4 | using UniverseLib.UI.ObjectPool; 5 | 6 | namespace UnityExplorer.UI.Widgets 7 | { 8 | public abstract class BaseArgumentHandler : IPooledObject 9 | { 10 | internal Text argNameLabel; 11 | internal InputFieldRef inputField; 12 | internal TypeCompleter typeCompleter; 13 | 14 | // IPooledObject 15 | public float DefaultHeight => 25f; 16 | public GameObject UIRoot { get; set; } 17 | 18 | public abstract void CreateSpecialContent(); 19 | 20 | public GameObject CreateContent(GameObject parent) 21 | { 22 | UIRoot = UIFactory.CreateUIObject("ArgRow", parent); 23 | UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleHeight: 50, minWidth: 50, flexibleWidth: 9999); 24 | UIFactory.SetLayoutGroup(UIRoot, false, false, true, true, 5); 25 | UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; 26 | 27 | argNameLabel = UIFactory.CreateLabel(UIRoot, "ArgLabel", "not set", TextAnchor.MiddleLeft); 28 | UIFactory.SetLayoutElement(argNameLabel.gameObject, minWidth: 40, flexibleWidth: 90, minHeight: 25, flexibleHeight: 50); 29 | argNameLabel.horizontalOverflow = HorizontalWrapMode.Wrap; 30 | 31 | inputField = UIFactory.CreateInputField(UIRoot, "InputField", "..."); 32 | UIFactory.SetLayoutElement(inputField.UIRoot, minHeight: 25, flexibleHeight: 50, minWidth: 100, flexibleWidth: 1000); 33 | inputField.Component.lineType = InputField.LineType.MultiLineNewline; 34 | inputField.UIRoot.AddComponent().verticalFit = ContentSizeFitter.FitMode.PreferredSize; 35 | 36 | typeCompleter = new TypeCompleter(typeof(object), this.inputField) 37 | { 38 | Enabled = false 39 | }; 40 | 41 | CreateSpecialContent(); 42 | 43 | return UIRoot; 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/UI/Widgets/EvaluateWidget/GenericArgumentHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Text; 2 | 3 | namespace UnityExplorer.UI.Widgets 4 | { 5 | public class GenericArgumentHandler : BaseArgumentHandler 6 | { 7 | private Type genericArgument; 8 | 9 | public void OnBorrowed(Type genericArgument) 10 | { 11 | this.genericArgument = genericArgument; 12 | 13 | typeCompleter.Enabled = true; 14 | typeCompleter.BaseType = this.genericArgument; 15 | typeCompleter.CacheTypes(); 16 | 17 | Type[] constraints = this.genericArgument.GetGenericParameterConstraints(); 18 | 19 | StringBuilder sb = new($"{this.genericArgument.Name}"); 20 | 21 | for (int i = 0; i < constraints.Length; i++) 22 | { 23 | if (i == 0) sb.Append(' ').Append('('); 24 | else sb.Append(',').Append(' '); 25 | 26 | sb.Append(SignatureHighlighter.Parse(constraints[i], false)); 27 | 28 | if (i + 1 == constraints.Length) 29 | sb.Append(')'); 30 | } 31 | 32 | argNameLabel.text = sb.ToString(); 33 | } 34 | 35 | public void OnReturned() 36 | { 37 | this.genericArgument = null; 38 | 39 | this.typeCompleter.Enabled = false; 40 | 41 | this.inputField.Text = ""; 42 | } 43 | 44 | public Type Evaluate() 45 | { 46 | return ReflectionUtility.GetTypeByName(this.inputField.Text) 47 | ?? throw new Exception($"Could not find any type by name '{this.inputField.Text}'!"); 48 | } 49 | 50 | public override void CreateSpecialContent() 51 | { 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/UI/Widgets/EvaluateWidget/GenericConstructorWidget.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI; 2 | using UniverseLib.UI.Models; 3 | using UniverseLib.UI.ObjectPool; 4 | 5 | namespace UnityExplorer.UI.Widgets 6 | { 7 | public class GenericConstructorWidget 8 | { 9 | GenericArgumentHandler[] handlers; 10 | 11 | Type[] currentGenericParameters; 12 | Action currentOnSubmit; 13 | Action currentOnCancel; 14 | 15 | public GameObject UIRoot; 16 | Text Title; 17 | GameObject ArgsHolder; 18 | 19 | public void Show(Action onSubmit, Action onCancel, Type genericTypeDefinition) 20 | { 21 | Title.text = $"Setting generic arguments for {SignatureHighlighter.Parse(genericTypeDefinition, false)}..."; 22 | 23 | OnShow(onSubmit, onCancel, genericTypeDefinition.GetGenericArguments()); 24 | } 25 | 26 | public void Show(Action onSubmit, Action onCancel, MethodInfo genericMethodDefinition) 27 | { 28 | Title.text = $"Setting generic arguments for {SignatureHighlighter.ParseMethod(genericMethodDefinition)}..."; 29 | 30 | OnShow(onSubmit, onCancel, genericMethodDefinition.GetGenericArguments()); 31 | } 32 | 33 | void OnShow(Action onSubmit, Action onCancel, Type[] genericParameters) 34 | { 35 | currentOnSubmit = onSubmit; 36 | currentOnCancel = onCancel; 37 | 38 | SetGenericParameters(genericParameters); 39 | } 40 | 41 | void SetGenericParameters(Type[] genericParameters) 42 | { 43 | currentGenericParameters = genericParameters; 44 | 45 | handlers = new GenericArgumentHandler[genericParameters.Length]; 46 | for (int i = 0; i < genericParameters.Length; i++) 47 | { 48 | Type type = genericParameters[i]; 49 | 50 | GenericArgumentHandler holder = handlers[i] = Pool.Borrow(); 51 | holder.UIRoot.transform.SetParent(ArgsHolder.transform, false); 52 | holder.OnBorrowed(type); 53 | } 54 | } 55 | 56 | public void TrySubmit() 57 | { 58 | Type[] args = new Type[currentGenericParameters.Length]; 59 | 60 | for (int i = 0; i < args.Length; i++) 61 | { 62 | GenericArgumentHandler handler = handlers[i]; 63 | Type arg; 64 | try 65 | { 66 | arg = handler.Evaluate(); 67 | if (arg == null) throw new Exception(); 68 | } 69 | catch 70 | { 71 | ExplorerCore.LogWarning($"Generic argument '{handler.inputField.Text}' is not a valid type."); 72 | return; 73 | } 74 | args[i] = arg; 75 | } 76 | 77 | OnClose(); 78 | currentOnSubmit(args); 79 | } 80 | 81 | public void Cancel() 82 | { 83 | OnClose(); 84 | 85 | currentOnCancel?.Invoke(); 86 | } 87 | 88 | void OnClose() 89 | { 90 | if (handlers != null) 91 | { 92 | foreach (GenericArgumentHandler widget in handlers) 93 | { 94 | widget.OnReturned(); 95 | Pool.Return(widget); 96 | } 97 | handlers = null; 98 | } 99 | } 100 | 101 | // UI Construction 102 | 103 | internal void ConstructUI(GameObject parent) 104 | { 105 | UIRoot = UIFactory.CreateVerticalGroup(parent, "GenericArgsHandler", false, false, true, true, 5, new Vector4(5, 5, 5, 5), 106 | childAlignment: TextAnchor.MiddleCenter); 107 | UIFactory.SetLayoutElement(UIRoot, flexibleWidth: 9999, flexibleHeight: 9999); 108 | 109 | ButtonRef submitButton = UIFactory.CreateButton(UIRoot, "SubmitButton", "Submit", new Color(0.2f, 0.3f, 0.2f)); 110 | UIFactory.SetLayoutElement(submitButton.GameObject, minHeight: 25, minWidth: 200); 111 | submitButton.OnClick += TrySubmit; 112 | 113 | ButtonRef cancelButton = UIFactory.CreateButton(UIRoot, "CancelButton", "Cancel", new Color(0.3f, 0.2f, 0.2f)); 114 | UIFactory.SetLayoutElement(cancelButton.GameObject, minHeight: 25, minWidth: 200); 115 | cancelButton.OnClick += Cancel; 116 | 117 | Title = UIFactory.CreateLabel(UIRoot, "Title", "Generic Arguments", TextAnchor.MiddleCenter); 118 | UIFactory.SetLayoutElement(Title.gameObject, minHeight: 25, flexibleWidth: 9999); 119 | 120 | GameObject scrollview = UIFactory.CreateScrollView(UIRoot, "GenericArgsScrollView", out ArgsHolder, out _, new(0.1f, 0.1f, 0.1f)); 121 | UIFactory.SetLayoutElement(scrollview, flexibleWidth: 9999, flexibleHeight: 9999); 122 | UIFactory.SetLayoutGroup(ArgsHolder, padTop: 5, padLeft: 5, padBottom: 5, padRight: 5); 123 | } 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/UI/Widgets/GameObjects/AxisControl.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI; 2 | using UniverseLib.UI.Models; 3 | 4 | namespace UnityExplorer.UI.Widgets 5 | { 6 | // Handles the slider and +/- buttons for a specific axis of a transform property. 7 | 8 | public class AxisControl 9 | { 10 | public readonly Vector3Control parent; 11 | 12 | public readonly int axis; 13 | public readonly Slider slider; 14 | 15 | public AxisControl(int axis, Slider slider, Vector3Control parentControl) 16 | { 17 | this.parent = parentControl; 18 | this.axis = axis; 19 | this.slider = slider; 20 | } 21 | 22 | void OnVectorSliderChanged(float value) 23 | { 24 | parent.Owner.CurrentSlidingAxisControl = value == 0f ? null : this; 25 | } 26 | 27 | void OnVectorMinusClicked() 28 | { 29 | parent.Owner.AxisControlOperation(-this.parent.Increment, this.parent, this.axis); 30 | } 31 | 32 | void OnVectorPlusClicked() 33 | { 34 | parent.Owner.AxisControlOperation(this.parent.Increment, this.parent, this.axis); 35 | } 36 | 37 | public static AxisControl Create(GameObject parent, string title, int axis, Vector3Control owner) 38 | { 39 | Text label = UIFactory.CreateLabel(parent, $"Label_{title}", $"{title}:", TextAnchor.MiddleRight, Color.grey); 40 | UIFactory.SetLayoutElement(label.gameObject, minHeight: 25, minWidth: 30); 41 | 42 | GameObject sliderObj = UIFactory.CreateSlider(parent, $"Slider_{title}", out Slider slider); 43 | UIFactory.SetLayoutElement(sliderObj, minHeight: 25, minWidth: 75, flexibleWidth: 0); 44 | slider.m_FillImage.color = Color.clear; 45 | 46 | slider.minValue = -0.1f; 47 | slider.maxValue = 0.1f; 48 | 49 | AxisControl sliderControl = new(axis, slider, owner); 50 | 51 | slider.onValueChanged.AddListener(sliderControl.OnVectorSliderChanged); 52 | 53 | ButtonRef minusButton = UIFactory.CreateButton(parent, "MinusIncrementButton", "-"); 54 | UIFactory.SetLayoutElement(minusButton.GameObject, minWidth: 20, flexibleWidth: 0, minHeight: 25); 55 | minusButton.OnClick += sliderControl.OnVectorMinusClicked; 56 | 57 | ButtonRef plusButton = UIFactory.CreateButton(parent, "PlusIncrementButton", "+"); 58 | UIFactory.SetLayoutElement(plusButton.GameObject, minWidth: 20, flexibleWidth: 0, minHeight: 25); 59 | plusButton.OnClick += sliderControl.OnVectorPlusClicked; 60 | 61 | return sliderControl; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/UI/Widgets/GameObjects/ComponentCell.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI; 2 | using UniverseLib.UI.Models; 3 | using UniverseLib.UI.Widgets.ButtonList; 4 | 5 | namespace UnityExplorer.UI.Widgets 6 | { 7 | public class ComponentCell : ButtonCell 8 | { 9 | public Toggle BehaviourToggle; 10 | public ButtonRef DestroyButton; 11 | 12 | public Action OnBehaviourToggled; 13 | public Action OnDestroyClicked; 14 | 15 | private void BehaviourToggled(bool val) 16 | { 17 | OnBehaviourToggled?.Invoke(val, CurrentDataIndex); 18 | } 19 | 20 | private void DestroyClicked() 21 | { 22 | OnDestroyClicked?.Invoke(CurrentDataIndex); 23 | } 24 | 25 | public override GameObject CreateContent(GameObject parent) 26 | { 27 | GameObject root = base.CreateContent(parent); 28 | 29 | // Add mask to button so text doesnt overlap on Close button 30 | //this.Button.Component.gameObject.AddComponent().showMaskGraphic = true; 31 | this.Button.ButtonText.horizontalOverflow = HorizontalWrapMode.Wrap; 32 | 33 | // Behaviour toggle 34 | 35 | GameObject toggleObj = UIFactory.CreateToggle(UIRoot, "BehaviourToggle", out BehaviourToggle, out Text behavText); 36 | UIFactory.SetLayoutElement(toggleObj, minHeight: 25, minWidth: 25); 37 | BehaviourToggle.onValueChanged.AddListener(BehaviourToggled); 38 | // put at first object 39 | toggleObj.transform.SetSiblingIndex(0); 40 | 41 | // Destroy button 42 | 43 | DestroyButton = UIFactory.CreateButton(UIRoot, "DestroyButton", "X", new Color(0.3f, 0.2f, 0.2f)); 44 | UIFactory.SetLayoutElement(DestroyButton.Component.gameObject, minHeight: 21, minWidth: 25); 45 | DestroyButton.OnClick += DestroyClicked; 46 | 47 | return root; 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/UI/Widgets/GameObjects/ComponentList.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Inspectors; 2 | using UniverseLib.UI.Widgets.ButtonList; 3 | using UniverseLib.UI.Widgets.ScrollView; 4 | 5 | namespace UnityExplorer.UI.Widgets 6 | { 7 | public class ComponentList : ButtonListHandler 8 | { 9 | public GameObjectInspector Parent; 10 | 11 | public ComponentList(ScrollPool scrollPool, Func> getEntriesMethod) 12 | : base(scrollPool, getEntriesMethod, null, null, null) 13 | { 14 | base.SetICell = SetComponentCell; 15 | base.ShouldDisplay = CheckShouldDisplay; 16 | base.OnCellClicked = OnComponentClicked; 17 | } 18 | 19 | public void Clear() 20 | { 21 | RefreshData(); 22 | ScrollPool.Refresh(true, true); 23 | } 24 | 25 | private bool CheckShouldDisplay(Component _, string __) => true; 26 | 27 | public override void OnCellBorrowed(ComponentCell cell) 28 | { 29 | base.OnCellBorrowed(cell); 30 | 31 | cell.OnBehaviourToggled += OnBehaviourToggled; 32 | cell.OnDestroyClicked += OnDestroyClicked; 33 | } 34 | 35 | public override void SetCell(ComponentCell cell, int index) 36 | { 37 | base.SetCell(cell, index); 38 | } 39 | 40 | private void OnComponentClicked(int index) 41 | { 42 | List entries = GetEntries(); 43 | 44 | if (index < 0 || index >= entries.Count) 45 | return; 46 | 47 | Component comp = entries[index]; 48 | if (comp) 49 | InspectorManager.Inspect(comp); 50 | } 51 | 52 | private void OnBehaviourToggled(bool value, int index) 53 | { 54 | try 55 | { 56 | List entries = GetEntries(); 57 | Component comp = entries[index]; 58 | 59 | if (comp.TryCast() is Behaviour behaviour) 60 | behaviour.enabled = value; 61 | } 62 | catch (Exception ex) 63 | { 64 | ExplorerCore.LogWarning($"Exception toggling Behaviour.enabled: {ex.ReflectionExToString()}"); 65 | } 66 | } 67 | 68 | private void OnDestroyClicked(int index) 69 | { 70 | try 71 | { 72 | List entries = GetEntries(); 73 | Component comp = entries[index]; 74 | 75 | GameObject.DestroyImmediate(comp); 76 | 77 | Parent.UpdateComponents(); 78 | } 79 | catch (Exception ex) 80 | { 81 | ExplorerCore.LogWarning($"Exception destroying Component: {ex.ReflectionExToString()}"); 82 | } 83 | } 84 | 85 | private static readonly Dictionary compToStringCache = new(); 86 | 87 | // Called from ButtonListHandler.SetCell, will be valid 88 | private void SetComponentCell(ComponentCell cell, int index) 89 | { 90 | List entries = GetEntries(); 91 | cell.Enable(); 92 | 93 | Component comp = entries[index]; 94 | Type type = comp.GetActualType(); 95 | 96 | if (!compToStringCache.ContainsKey(type.AssemblyQualifiedName)) 97 | compToStringCache.Add(type.AssemblyQualifiedName, SignatureHighlighter.Parse(type, true)); 98 | 99 | cell.Button.ButtonText.text = compToStringCache[type.AssemblyQualifiedName]; 100 | 101 | if (typeof(Behaviour).IsAssignableFrom(type)) 102 | { 103 | cell.BehaviourToggle.interactable = true; 104 | cell.BehaviourToggle.Set(comp.TryCast().enabled, false); 105 | cell.BehaviourToggle.graphic.color = new Color(0.8f, 1, 0.8f, 0.3f); 106 | } 107 | else 108 | { 109 | cell.BehaviourToggle.interactable = false; 110 | cell.BehaviourToggle.Set(true, false); 111 | //RuntimeHelper.SetColorBlock(cell.BehaviourToggle,) 112 | cell.BehaviourToggle.graphic.color = new Color(0.2f, 0.2f, 0.2f); 113 | } 114 | 115 | // if component is the first index it must be the transform, dont show Destroy button for it. 116 | if (index == 0 && cell.DestroyButton.Component.gameObject.activeSelf) 117 | cell.DestroyButton.Component.gameObject.SetActive(false); 118 | else if (index > 0 && !cell.DestroyButton.Component.gameObject.activeSelf) 119 | cell.DestroyButton.Component.gameObject.SetActive(true); 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/UI/Widgets/GameObjects/GameObjectControls.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Inspectors; 2 | 3 | namespace UnityExplorer.UI.Widgets 4 | { 5 | // The base wrapper to hold a reference to the parent Inspector and the GameObjectInfo and TransformControls widgets. 6 | 7 | public class GameObjectControls 8 | { 9 | public GameObjectInspector Parent { get; } 10 | public GameObject Target => Parent.Target; 11 | 12 | public GameObjectInfoPanel GameObjectInfo { get; } 13 | 14 | public TransformControls TransformControl { get; } 15 | 16 | public GameObjectControls(GameObjectInspector parent) 17 | { 18 | this.Parent = parent; 19 | 20 | this.GameObjectInfo = new(this); 21 | this.TransformControl = new(this); 22 | } 23 | 24 | public void UpdateGameObjectInfo(bool firstUpdate, bool force) 25 | { 26 | GameObjectInfo.UpdateGameObjectInfo(firstUpdate, force); 27 | } 28 | 29 | public void UpdateVectorSlider() 30 | { 31 | TransformControl.UpdateVectorSlider(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/UI/Widgets/GameObjects/TransformControls.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.Input; 2 | using UniverseLib.UI; 3 | 4 | namespace UnityExplorer.UI.Widgets 5 | { 6 | // Handles axis change operations and holds references to the Vector3Controls for each transform property 7 | 8 | public class TransformControls 9 | { 10 | public GameObjectControls Owner { get; } 11 | GameObject Target => Owner.Target; 12 | 13 | public AxisControl CurrentSlidingAxisControl { get; set; } 14 | 15 | Vector3Control PositionControl; 16 | Vector3Control LocalPositionControl; 17 | Vector3Control RotationControl; 18 | Vector3Control ScaleControl; 19 | 20 | public TransformControls(GameObjectControls owner) 21 | { 22 | this.Owner = owner; 23 | Create(); 24 | } 25 | 26 | public void UpdateTransformControlValues(bool force) 27 | { 28 | PositionControl.Update(force); 29 | LocalPositionControl.Update(force); 30 | RotationControl.Update(force); 31 | ScaleControl.Update(force); 32 | } 33 | 34 | public void UpdateVectorSlider() 35 | { 36 | AxisControl control = CurrentSlidingAxisControl; 37 | 38 | if (control == null) 39 | return; 40 | 41 | if (!InputManager.GetMouseButton(0)) 42 | { 43 | control.slider.value = 0f; 44 | control = null; 45 | return; 46 | } 47 | 48 | AxisControlOperation(control.slider.value, control.parent, control.axis); 49 | } 50 | 51 | public void AxisControlOperation(float value, Vector3Control parent, int axis) 52 | { 53 | Transform transform = Target.transform; 54 | 55 | Vector3 vector = parent.Type switch 56 | { 57 | TransformType.Position => transform.position, 58 | TransformType.LocalPosition => transform.localPosition, 59 | TransformType.Rotation => transform.localEulerAngles, 60 | TransformType.Scale => transform.localScale, 61 | _ => throw new NotImplementedException() 62 | }; 63 | 64 | // apply vector value change 65 | switch (axis) 66 | { 67 | case 0: 68 | vector.x += value; break; 69 | case 1: 70 | vector.y += value; break; 71 | case 2: 72 | vector.z += value; break; 73 | } 74 | 75 | // set vector back to transform 76 | switch (parent.Type) 77 | { 78 | case TransformType.Position: 79 | transform.position = vector; break; 80 | case TransformType.LocalPosition: 81 | transform.localPosition = vector; break; 82 | case TransformType.Rotation: 83 | transform.localEulerAngles = vector; break; 84 | case TransformType.Scale: 85 | transform.localScale = vector; break; 86 | } 87 | 88 | UpdateTransformControlValues(false); 89 | } 90 | 91 | public void Create() 92 | { 93 | GameObject transformGroup = UIFactory.CreateVerticalGroup(Owner.Parent.Content, "TransformControls", false, false, true, true, 2, 94 | new Vector4(2, 2, 0, 0), new Color(0.1f, 0.1f, 0.1f)); 95 | UIFactory.SetLayoutElement(transformGroup, minHeight: 100, flexibleWidth: 9999); 96 | 97 | PositionControl = Vector3Control.Create(this, transformGroup, "Position:", TransformType.Position); 98 | LocalPositionControl = Vector3Control.Create(this, transformGroup, "Local Position:", TransformType.LocalPosition); 99 | RotationControl = Vector3Control.Create(this, transformGroup, "Rotation:", TransformType.Rotation); 100 | ScaleControl = Vector3Control.Create(this, transformGroup, "Scale:", TransformType.Scale); 101 | } 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/UI/Widgets/GameObjects/TransformType.cs: -------------------------------------------------------------------------------- 1 | namespace UnityExplorer.UI.Widgets 2 | { 3 | public enum TransformType { Position, LocalPosition, Rotation, Scale } 4 | } 5 | -------------------------------------------------------------------------------- /src/UI/Widgets/GameObjects/Vector3Control.cs: -------------------------------------------------------------------------------- 1 | using UniverseLib.UI; 2 | using UniverseLib.UI.Models; 3 | 4 | namespace UnityExplorer.UI.Widgets 5 | { 6 | // Controls a Vector3 property of a Transform, and holds references to each AxisControl for X/Y/Z. 7 | 8 | public class Vector3Control 9 | { 10 | public TransformControls Owner { get; } 11 | public GameObject Target => Owner.Owner.Target; 12 | public Transform Transform => Target.transform; 13 | public TransformType Type { get; } 14 | 15 | public InputFieldRef MainInput { get; } 16 | 17 | public AxisControl[] AxisControls { get; } = new AxisControl[3]; 18 | 19 | public InputFieldRef IncrementInput { get; set; } 20 | public float Increment { get; set; } = 0.1f; 21 | 22 | Vector3 lastValue; 23 | 24 | Vector3 CurrentValue => Type switch 25 | { 26 | TransformType.Position => Transform.position, 27 | TransformType.LocalPosition => Transform.localPosition, 28 | TransformType.Rotation => Transform.localEulerAngles, 29 | TransformType.Scale => Transform.localScale, 30 | _ => throw new NotImplementedException() 31 | }; 32 | 33 | public Vector3Control(TransformControls owner, TransformType type, InputFieldRef input) 34 | { 35 | this.Owner = owner; 36 | this.Type = type; 37 | this.MainInput = input; 38 | } 39 | 40 | public void Update(bool force) 41 | { 42 | Vector3 currValue = CurrentValue; 43 | if (force || (!MainInput.Component.isFocused && !lastValue.Equals(currValue))) 44 | { 45 | MainInput.Text = ParseUtility.ToStringForInput(currValue); 46 | lastValue = currValue; 47 | } 48 | } 49 | 50 | void OnTransformInputEndEdit(TransformType type, string input) 51 | { 52 | switch (type) 53 | { 54 | case TransformType.Position: 55 | { 56 | if (ParseUtility.TryParse(input, out Vector3 val, out _)) 57 | Target.transform.position = val; 58 | } 59 | break; 60 | case TransformType.LocalPosition: 61 | { 62 | if (ParseUtility.TryParse(input, out Vector3 val, out _)) 63 | Target.transform.localPosition = val; 64 | } 65 | break; 66 | case TransformType.Rotation: 67 | { 68 | if (ParseUtility.TryParse(input, out Vector3 val, out _)) 69 | Target.transform.localEulerAngles = val; 70 | } 71 | break; 72 | case TransformType.Scale: 73 | { 74 | if (ParseUtility.TryParse(input, out Vector3 val, out _)) 75 | Target.transform.localScale = val; 76 | } 77 | break; 78 | } 79 | 80 | Owner.UpdateTransformControlValues(true); 81 | } 82 | 83 | void IncrementInput_OnEndEdit(string value) 84 | { 85 | if (!ParseUtility.TryParse(value, out float increment, out _)) 86 | IncrementInput.Text = ParseUtility.ToStringForInput(Increment); 87 | else 88 | { 89 | Increment = increment; 90 | foreach (AxisControl slider in AxisControls) 91 | { 92 | slider.slider.minValue = -increment; 93 | slider.slider.maxValue = increment; 94 | } 95 | } 96 | } 97 | 98 | public static Vector3Control Create(TransformControls owner, GameObject transformGroup, string title, TransformType type) 99 | { 100 | GameObject rowObj = UIFactory.CreateUIObject($"Row_{title}", transformGroup); 101 | UIFactory.SetLayoutGroup(rowObj, false, false, true, true, 5, 0, 0, 0, 0, default); 102 | UIFactory.SetLayoutElement(rowObj, minHeight: 25, flexibleWidth: 9999); 103 | 104 | Text titleLabel = UIFactory.CreateLabel(rowObj, "PositionLabel", title, TextAnchor.MiddleRight, Color.grey); 105 | UIFactory.SetLayoutElement(titleLabel.gameObject, minHeight: 25, minWidth: 110); 106 | 107 | InputFieldRef inputField = UIFactory.CreateInputField(rowObj, "InputField", "..."); 108 | UIFactory.SetLayoutElement(inputField.Component.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 999); 109 | 110 | Vector3Control control = new(owner, type, inputField); 111 | 112 | inputField.Component.GetOnEndEdit().AddListener((string value) => { control.OnTransformInputEndEdit(type, value); }); 113 | 114 | control.AxisControls[0] = AxisControl.Create(rowObj, "X", 0, control); 115 | control.AxisControls[1] = AxisControl.Create(rowObj, "Y", 1, control); 116 | control.AxisControls[2] = AxisControl.Create(rowObj, "Z", 2, control); 117 | 118 | control.IncrementInput = UIFactory.CreateInputField(rowObj, "IncrementInput", "..."); 119 | control.IncrementInput.Text = "0.1"; 120 | UIFactory.SetLayoutElement(control.IncrementInput.GameObject, minWidth: 30, flexibleWidth: 0, minHeight: 25); 121 | control.IncrementInput.Component.GetOnEndEdit().AddListener(control.IncrementInput_OnEndEdit); 122 | 123 | return control; 124 | } 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/UI/Widgets/TimeScaleWidget.cs: -------------------------------------------------------------------------------- 1 | using HarmonyLib; 2 | using UniverseLib.UI; 3 | using UniverseLib.UI.Models; 4 | #if UNHOLLOWER 5 | using IL2CPPUtils = UnhollowerBaseLib.UnhollowerUtils; 6 | #endif 7 | #if INTEROP 8 | using IL2CPPUtils = Il2CppInterop.Common.Il2CppInteropUtils; 9 | #endif 10 | 11 | namespace UnityExplorer.UI.Widgets 12 | { 13 | internal class TimeScaleWidget 14 | { 15 | public TimeScaleWidget(GameObject parent) 16 | { 17 | Instance = this; 18 | 19 | ConstructUI(parent); 20 | 21 | InitPatch(); 22 | } 23 | 24 | static TimeScaleWidget Instance; 25 | 26 | ButtonRef lockBtn; 27 | bool locked; 28 | InputFieldRef timeInput; 29 | float desiredTime; 30 | bool settingTimeScale; 31 | 32 | public void Update() 33 | { 34 | // Fallback in case Time.timeScale patch failed for whatever reason 35 | if (locked) 36 | SetTimeScale(desiredTime); 37 | 38 | if (!timeInput.Component.isFocused) 39 | timeInput.Text = Time.timeScale.ToString("F2"); 40 | } 41 | 42 | void SetTimeScale(float time) 43 | { 44 | settingTimeScale = true; 45 | Time.timeScale = time; 46 | settingTimeScale = false; 47 | } 48 | 49 | // UI event listeners 50 | 51 | void OnTimeInputEndEdit(string val) 52 | { 53 | if (float.TryParse(val, out float f)) 54 | { 55 | SetTimeScale(f); 56 | desiredTime = f; 57 | } 58 | } 59 | 60 | void OnPauseButtonClicked() 61 | { 62 | OnTimeInputEndEdit(timeInput.Text); 63 | 64 | locked = !locked; 65 | 66 | Color color = locked ? new Color(0.3f, 0.3f, 0.2f) : new Color(0.2f, 0.2f, 0.2f); 67 | RuntimeHelper.SetColorBlock(lockBtn.Component, color, color * 1.2f, color * 0.7f); 68 | lockBtn.ButtonText.text = locked ? "Unlock" : "Lock"; 69 | } 70 | 71 | // UI Construction 72 | 73 | void ConstructUI(GameObject parent) 74 | { 75 | Text timeLabel = UIFactory.CreateLabel(parent, "TimeLabel", "Time:", TextAnchor.MiddleRight, Color.grey); 76 | UIFactory.SetLayoutElement(timeLabel.gameObject, minHeight: 25, minWidth: 35); 77 | 78 | timeInput = UIFactory.CreateInputField(parent, "TimeInput", "timeScale"); 79 | UIFactory.SetLayoutElement(timeInput.Component.gameObject, minHeight: 25, minWidth: 40); 80 | timeInput.Component.GetOnEndEdit().AddListener(OnTimeInputEndEdit); 81 | 82 | timeInput.Text = string.Empty; 83 | timeInput.Text = Time.timeScale.ToString(); 84 | 85 | lockBtn = UIFactory.CreateButton(parent, "PauseButton", "Lock", new Color(0.2f, 0.2f, 0.2f)); 86 | UIFactory.SetLayoutElement(lockBtn.Component.gameObject, minHeight: 25, minWidth: 50); 87 | lockBtn.OnClick += OnPauseButtonClicked; 88 | } 89 | 90 | // Only allow Time.timeScale to be set if the user hasn't "locked" it or if we are setting the value internally. 91 | 92 | static void InitPatch() 93 | { 94 | 95 | try 96 | { 97 | MethodInfo target = typeof(Time).GetProperty("timeScale").GetSetMethod(); 98 | #if CPP 99 | if (IL2CPPUtils.GetIl2CppMethodInfoPointerFieldForGeneratedMethod(target) == null) 100 | return; 101 | #endif 102 | ExplorerCore.Harmony.Patch(target, 103 | prefix: new(AccessTools.Method(typeof(TimeScaleWidget), nameof(Prefix_Time_set_timeScale)))); 104 | } 105 | catch { } 106 | } 107 | 108 | static bool Prefix_Time_set_timeScale() 109 | { 110 | return !Instance.locked || Instance.settingTimeScale; 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/UI/Widgets/UnityObjects/UnityObjectWidget.cs: -------------------------------------------------------------------------------- 1 | using UnityExplorer.Inspectors; 2 | using UniverseLib.UI; 3 | using UniverseLib.UI.Models; 4 | using UniverseLib.UI.ObjectPool; 5 | 6 | namespace UnityExplorer.UI.Widgets 7 | { 8 | public class UnityObjectWidget : IPooledObject 9 | { 10 | public UnityEngine.Object unityObject; 11 | public Component component; 12 | public ReflectionInspector owner; 13 | 14 | protected ButtonRef gameObjectButton; 15 | protected InputFieldRef nameInput; 16 | protected InputFieldRef instanceIdInput; 17 | 18 | // IPooledObject 19 | public GameObject UIRoot { get; set; } 20 | public float DefaultHeight => -1; 21 | 22 | public static UnityObjectWidget GetUnityWidget(object target, Type targetType, ReflectionInspector inspector) 23 | { 24 | if (!typeof(UnityEngine.Object).IsAssignableFrom(targetType)) 25 | return null; 26 | 27 | UnityObjectWidget widget = target switch 28 | { 29 | Texture2D or Cubemap => Pool.Borrow(), 30 | Sprite s when s.texture => Pool.Borrow(), 31 | Image i when i.sprite?.texture => Pool.Borrow(), 32 | 33 | Material when MaterialWidget.MaterialWidgetSupported => Pool.Borrow(), 34 | 35 | AudioClip => Pool.Borrow(), 36 | 37 | _ => Pool.Borrow() 38 | }; 39 | 40 | widget.OnBorrowed(target, targetType, inspector); 41 | 42 | return widget; 43 | } 44 | 45 | public virtual void OnBorrowed(object target, Type targetType, ReflectionInspector inspector) 46 | { 47 | this.owner = inspector; 48 | 49 | if (!this.UIRoot) 50 | CreateContent(inspector.UIRoot); 51 | else 52 | this.UIRoot.transform.SetParent(inspector.UIRoot.transform); 53 | 54 | this.UIRoot.transform.SetSiblingIndex(inspector.UIRoot.transform.childCount - 2); 55 | 56 | unityObject = target.TryCast(); 57 | UIRoot.SetActive(true); 58 | 59 | nameInput.Text = unityObject.name; 60 | instanceIdInput.Text = unityObject.GetInstanceID().ToString(); 61 | 62 | if (typeof(Component).IsAssignableFrom(targetType)) 63 | { 64 | component = (Component)target.TryCast(typeof(Component)); 65 | gameObjectButton.Component.gameObject.SetActive(true); 66 | } 67 | else 68 | gameObjectButton.Component.gameObject.SetActive(false); 69 | } 70 | 71 | public virtual void OnReturnToPool() 72 | { 73 | unityObject = null; 74 | component = null; 75 | owner = null; 76 | } 77 | 78 | // Update 79 | 80 | public virtual void Update() 81 | { 82 | if (this.unityObject) 83 | { 84 | nameInput.Text = unityObject.name; 85 | 86 | owner.Tab.TabText.text = $"{owner.TabButtonText} \"{unityObject.name}\""; 87 | } 88 | } 89 | 90 | // UI Listeners 91 | 92 | private void OnGameObjectButtonClicked() 93 | { 94 | if (!component) 95 | { 96 | ExplorerCore.LogWarning("Component reference is null or destroyed!"); 97 | return; 98 | } 99 | 100 | InspectorManager.Inspect(component.gameObject); 101 | } 102 | 103 | // UI construction 104 | 105 | public virtual GameObject CreateContent(GameObject uiRoot) 106 | { 107 | UIRoot = UIFactory.CreateUIObject("UnityObjectRow", uiRoot); 108 | UIFactory.SetLayoutGroup(UIRoot, false, false, true, true, 5); 109 | UIFactory.SetLayoutElement(UIRoot, minHeight: 25, flexibleHeight: 0, flexibleWidth: 9999); 110 | 111 | Text nameLabel = UIFactory.CreateLabel(UIRoot, "NameLabel", "Name:", TextAnchor.MiddleLeft, Color.grey); 112 | UIFactory.SetLayoutElement(nameLabel.gameObject, minHeight: 25, minWidth: 45, flexibleWidth: 0); 113 | 114 | nameInput = UIFactory.CreateInputField(UIRoot, "NameInput", "untitled"); 115 | UIFactory.SetLayoutElement(nameInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 1000); 116 | nameInput.Component.readOnly = true; 117 | 118 | gameObjectButton = UIFactory.CreateButton(UIRoot, "GameObjectButton", "Inspect GameObject", new Color(0.2f, 0.2f, 0.2f)); 119 | UIFactory.SetLayoutElement(gameObjectButton.Component.gameObject, minHeight: 25, minWidth: 160); 120 | gameObjectButton.OnClick += OnGameObjectButtonClicked; 121 | 122 | Text instanceLabel = UIFactory.CreateLabel(UIRoot, "InstanceLabel", "Instance ID:", TextAnchor.MiddleRight, Color.grey); 123 | UIFactory.SetLayoutElement(instanceLabel.gameObject, minHeight: 25, minWidth: 100, flexibleWidth: 0); 124 | 125 | instanceIdInput = UIFactory.CreateInputField(UIRoot, "InstanceIDInput", "ERROR"); 126 | UIFactory.SetLayoutElement(instanceIdInput.UIRoot, minHeight: 25, minWidth: 100, flexibleWidth: 0); 127 | instanceIdInput.Component.readOnly = true; 128 | 129 | UIRoot.SetActive(false); 130 | 131 | return UIRoot; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/UnityExplorer.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.1.32328.378 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UnityExplorer", "UnityExplorer.csproj", "{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Release_BIE_Cpp|Any CPU = Release_BIE_Cpp|Any CPU 11 | Release_BIE_CoreCLR|Any CPU = Release_BIE_CoreCLR|Any CPU 12 | Release_BIE5_Mono|Any CPU = Release_BIE5_Mono|Any CPU 13 | Release_BIE6_Mono|Any CPU = Release_BIE6_Mono|Any CPU 14 | Release_ML_Cpp_net472|Any CPU = Release_ML_Cpp_net472|Any CPU 15 | Release_ML_Cpp_net6|Any CPU = Release_ML_Cpp_net6|Any CPU 16 | Release_ML_Cpp_net6_interop|Any CPU = Release_ML_Cpp_net6_interop|Any CPU 17 | Release_ML_Mono|Any CPU = Release_ML_Mono|Any CPU 18 | Release_STANDALONE_Cpp|Any CPU = Release_STANDALONE_Cpp|Any CPU 19 | Release_STANDALONE_Mono|Any CPU = Release_STANDALONE_Mono|Any CPU 20 | EndGlobalSection 21 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 22 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Cpp|Any CPU.ActiveCfg = BIE_Cpp|Any CPU 23 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Cpp|Any CPU.Build.0 = BIE_Cpp|Any CPU 24 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_CoreCLR|Any CPU.ActiveCfg = BIE_Cpp_CoreCLR|Any CPU 25 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_CoreCLR|Any CPU.Build.0 = BIE_Cpp_CoreCLR|Any CPU 26 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE5_Mono|Any CPU.ActiveCfg = BIE5_Mono|Any CPU 27 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE5_Mono|Any CPU.Build.0 = BIE5_Mono|Any CPU 28 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE6_Mono|Any CPU.ActiveCfg = BIE6_Mono|Any CPU 29 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE6_Mono|Any CPU.Build.0 = BIE6_Mono|Any CPU 30 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Cpp_net472|Any CPU.ActiveCfg = ML_Cpp_net472|Any CPU 31 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Cpp_net472|Any CPU.Build.0 = ML_Cpp_net472|Any CPU 32 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Cpp_net6|Any CPU.ActiveCfg = ML_Cpp_net6|Any CPU 33 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Cpp_net6|Any CPU.Build.0 = ML_Cpp_net6|Any CPU 34 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Cpp_net6_interop|Any CPU.ActiveCfg = ML_Cpp_net6_interop|Any CPU 35 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Cpp_net6_interop|Any CPU.Build.0 = ML_Cpp_net6_interop|Any CPU 36 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Mono|Any CPU.ActiveCfg = ML_Mono|Any CPU 37 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Mono|Any CPU.Build.0 = ML_Mono|Any CPU 38 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Cpp|Any CPU.ActiveCfg = STANDALONE_Cpp|Any CPU 39 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Cpp|Any CPU.Build.0 = STANDALONE_Cpp|Any CPU 40 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Mono|Any CPU.ActiveCfg = STANDALONE_Mono|Any CPU 41 | {B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Mono|Any CPU.Build.0 = STANDALONE_Mono|Any CPU 42 | EndGlobalSection 43 | GlobalSection(SolutionProperties) = preSolution 44 | HideSolutionNode = FALSE 45 | EndGlobalSection 46 | GlobalSection(ExtensibilityGlobals) = postSolution 47 | SolutionGuid = {DD5C0A5D-03F1-4CC3-8B4D-E10834908C5A} 48 | EndGlobalSection 49 | EndGlobal 50 | -------------------------------------------------------------------------------- /src/nuget.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | --------------------------------------------------------------------------------