├── .github └── workflows │ └── nuget.yml ├── .gitignore ├── .gitmodules ├── Directory.Build.props ├── LICENSE ├── README.md ├── RuntimeUnityEditor.Bepin5 ├── LogViewer │ ├── LogViewerEntry.cs │ ├── LogViewerListener.cs │ └── LogViewerWindow.cs ├── RuntimeUnityEditor.Bepin5.csproj └── RuntimeUnityEditor5.cs ├── RuntimeUnityEditor.Bepin6.IL2CPP ├── LogViewer │ ├── LogViewerEntry.cs │ ├── LogViewerListener.cs │ └── LogViewerWindow.cs ├── RuntimeUnityEditor.Bepin6.IL2CPP.csproj └── RuntimeUnityEditorPluginIL2CPP.cs ├── RuntimeUnityEditor.Core.IL2CPP ├── Libs │ └── Unity.InputSystem.dll ├── RuntimeUnityEditor.Core.IL2CPP.csproj └── RuntimeUnityEditor.Core.IL2CPP.nuspec ├── RuntimeUnityEditor.Core.Mono ├── RuntimeUnityEditor.Core.Mono.csproj ├── RuntimeUnityEditor.Core.Mono.nuspec └── packages.config ├── RuntimeUnityEditor.Core ├── AssemblyInfo.cs ├── FeatureBase.cs ├── Features │ ├── ContextMenu.cs │ ├── CursorUnlocker.cs │ ├── Gizmos │ │ ├── GizmoDrawer.cs │ │ └── lib │ │ │ ├── Constants.cs │ │ │ ├── Drawer.cs │ │ │ ├── Drawers │ │ │ ├── ArcDrawer.cs │ │ │ ├── CubeDrawer.cs │ │ │ ├── LineDrawer.cs │ │ │ ├── PolygonDrawer.cs │ │ │ └── SquareDrawer.cs │ │ │ ├── Element.cs │ │ │ ├── Gizmos.cs │ │ │ └── GizmosInstance.cs │ ├── MouseInspect.cs │ └── WireframeFeature.cs ├── RuntimeUnityEditor.Core.projitems ├── RuntimeUnityEditor.Core.shproj ├── RuntimeUnityEditorCore.cs ├── ScreenPartition.cs ├── Utils │ ├── Abstractions │ │ ├── AssetBundleManagerHelper.cs │ │ ├── DataUtility.cs │ │ ├── DnSpyHelper.cs │ │ ├── IL2CppAbstractions.cs │ │ ├── IL2CppExtensions.cs │ │ ├── ILoggerWrapper.cs │ │ ├── InitSettings.cs │ │ ├── LogLevel.cs │ │ ├── OpenFileDialog.cs │ │ ├── UnityFeatureHelper.cs │ │ └── UnityInput.cs │ ├── ComboBox.cs │ ├── Extensions.cs │ ├── IMGUIUtils.cs │ ├── ImguiComboBox.cs │ ├── MovingAverage.cs │ ├── ObjectDumper │ │ ├── Dumper.cs │ │ ├── MeshExport.cs │ │ └── ObjectDumperExtensions.cs │ ├── OrderedSet.cs │ ├── ReflectionUtils.cs │ ├── ResourceUtils.cs │ ├── TextureUtils.cs │ ├── ThreadingExtensions.cs │ ├── TomlTypeConverter.cs │ ├── TypeNameExtensions.cs │ └── UI │ │ ├── InterfaceMaker.cs │ │ ├── guisharp-box.png │ │ ├── guisharp-box.xcf │ │ ├── guisharp-window.png │ │ └── guisharp-window.xcf ├── WindowBase.cs ├── WindowManager.cs └── Windows │ ├── Breakpoints │ ├── BreakpointHit.cs │ ├── BreakpointHitException.cs │ ├── BreakpointPatchInfo.cs │ ├── Breakpoints.cs │ ├── BreakpointsWindow.cs │ └── DebuggerBreakType.cs │ ├── ChangeHistory │ ├── Change.cs │ ├── ChangeAction.cs │ ├── ChangeAssignment.cs │ ├── ChangeHistoryWindow.cs │ └── IChange.cs │ ├── Clipboard │ └── ClipboardWindow.cs │ ├── Inspector │ ├── Entries │ │ ├── Contents │ │ │ ├── CacheEntryBase.cs │ │ │ ├── CallbackCacheEntey.cs │ │ │ ├── EventCacheEntry.cs │ │ │ ├── FieldCacheEntry.cs │ │ │ ├── ICacheEntry.cs │ │ │ ├── ListCacheEntry.cs │ │ │ ├── MethodCacheEntry.cs │ │ │ ├── PropertyCacheEntry.cs │ │ │ ├── ReadonlyCacheEntry.cs │ │ │ └── ReadonlyListCacheEntry.cs │ │ └── Inspector │ │ │ ├── InspectorStackEntryBase.cs │ │ │ ├── InstanceStackEntry.cs │ │ │ └── StaticStackEntry.cs │ ├── Inspector.InspectorTab.cs │ ├── Inspector.cs │ ├── InspectorHelpObject.cs │ ├── ToStringConverter.cs │ └── VariableFieldDrawer.cs │ ├── ObjectTree │ ├── ObjectTreeViewer.cs │ └── RootGameObjectSearcher.cs │ ├── ObjectView │ └── ObjectViewWindow.cs │ ├── Profiler │ └── ProfilerWindow.cs │ ├── REPL │ ├── MCS │ │ └── ScriptEvaluator.cs │ ├── REPL.cs │ ├── REPLWindow.cs │ ├── ReplHelper.cs │ ├── Suggestion.cs │ ├── SuggestionKind.cs │ └── TypeHelper.cs │ └── Taskbar.cs ├── RuntimeUnityEditor.UMM ├── Info.json ├── Repository.json ├── RuntimeUnityEditor.UMM.csproj ├── RuntimeUnityEditorSettings.cs └── RuntimeUnityEditorUMM.cs ├── RuntimeUnityEditor.sln ├── install.ps1 ├── modules └── Directory.Build.props └── nuget.config /.github/workflows/nuget.yml: -------------------------------------------------------------------------------- 1 | name: Push to nuget feed on release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | workflow_dispatch: 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: windows-latest 13 | 14 | env: 15 | NUGET_ENABLE_LEGACY_CSPROJ_PACK: true 16 | 17 | steps: 18 | - name: Checkout 19 | uses: actions/checkout@v1 20 | with: 21 | submodules: true 22 | 23 | - name: Setup Nuget 24 | uses: nuget/setup-nuget@v1 25 | 26 | - name: Nuget pack 27 | working-directory: . 28 | run: | 29 | nuget restore 30 | nuget restore modules\mcs\mcs.sln 31 | gci -Recurse -Filter *.nuspec | foreach { nuget pack "$($_.DirectoryName)\$($_.BaseName).csproj" -build -properties Configuration=Release } 32 | 33 | - name: Nuget push 34 | working-directory: . 35 | env: 36 | NUGET_URL: https://pkgs.dev.azure.com/IllusionMods/Nuget/_packaging/IllusionMods/nuget/v3/index.json 37 | NUGET_TOKEN: ${{ secrets.NUGET_TOKEN }} 38 | run: | 39 | nuget sources update -name "IllusionMods" -username "token" -password ${env:NUGET_TOKEN} 40 | nuget push *.nupkg -apikey key -noninteractive -skipduplicate -src ${env:NUGET_URL} 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.suo 8 | *.user 9 | *.userosscache 10 | *.sln.docstates 11 | 12 | # User-specific files (MonoDevelop/Xamarin Studio) 13 | *.userprefs 14 | 15 | # Build results 16 | [Dd]ebug/ 17 | [Dd]ebugPublic/ 18 | [Rr]elease/ 19 | [Rr]eleases/ 20 | x64/ 21 | x86/ 22 | bld/ 23 | [Bb]in/ 24 | [Oo]bj/ 25 | [Ll]og/ 26 | 27 | # Visual Studio 2015/2017 cache/options directory 28 | .vs/ 29 | # Uncomment if you have tasks that create the project's static files in wwwroot 30 | #wwwroot/ 31 | 32 | # Visual Studio 2017 auto generated files 33 | Generated\ Files/ 34 | 35 | # MSTest test Results 36 | [Tt]est[Rr]esult*/ 37 | [Bb]uild[Ll]og.* 38 | 39 | # NUNIT 40 | *.VisualState.xml 41 | TestResult.xml 42 | 43 | # Build Results of an ATL Project 44 | [Dd]ebugPS/ 45 | [Rr]eleasePS/ 46 | dlldata.c 47 | 48 | # Benchmark Results 49 | BenchmarkDotNet.Artifacts/ 50 | 51 | # .NET Core 52 | project.lock.json 53 | project.fragment.lock.json 54 | artifacts/ 55 | **/Properties/launchSettings.json 56 | 57 | # StyleCop 58 | StyleCopReport.xml 59 | 60 | # Files built by Visual Studio 61 | *_i.c 62 | *_p.c 63 | *_i.h 64 | *.ilk 65 | *.meta 66 | *.obj 67 | *.iobj 68 | *.pch 69 | *.pdb 70 | *.ipdb 71 | *.pgc 72 | *.pgd 73 | *.rsp 74 | *.sbr 75 | *.tlb 76 | *.tli 77 | *.tlh 78 | *.tmp 79 | *.tmp_proj 80 | *.log 81 | *.vspscc 82 | *.vssscc 83 | .builds 84 | *.pidb 85 | *.svclog 86 | *.scc 87 | 88 | # Chutzpah Test files 89 | _Chutzpah* 90 | 91 | # Visual C++ cache files 92 | ipch/ 93 | *.aps 94 | *.ncb 95 | *.opendb 96 | *.opensdf 97 | *.sdf 98 | *.cachefile 99 | *.VC.db 100 | *.VC.VC.opendb 101 | 102 | # Visual Studio profiler 103 | *.psess 104 | *.vsp 105 | *.vspx 106 | *.sap 107 | 108 | # Visual Studio Trace Files 109 | *.e2e 110 | 111 | # TFS 2012 Local Workspace 112 | $tf/ 113 | 114 | # Guidance Automation Toolkit 115 | *.gpState 116 | 117 | # ReSharper is a .NET coding add-in 118 | _ReSharper*/ 119 | *.[Rr]e[Ss]harper 120 | *.DotSettings.user 121 | 122 | # JustCode is a .NET coding add-in 123 | .JustCode 124 | 125 | # TeamCity is a build add-in 126 | _TeamCity* 127 | 128 | # DotCover is a Code Coverage Tool 129 | *.dotCover 130 | 131 | # AxoCover is a Code Coverage Tool 132 | .axoCover/* 133 | !.axoCover/settings.json 134 | 135 | # Visual Studio code coverage results 136 | *.coverage 137 | *.coveragexml 138 | 139 | # NCrunch 140 | _NCrunch_* 141 | .*crunch*.local.xml 142 | nCrunchTemp_* 143 | 144 | # MightyMoose 145 | *.mm.* 146 | AutoTest.Net/ 147 | 148 | # Web workbench (sass) 149 | .sass-cache/ 150 | 151 | # Installshield output folder 152 | [Ee]xpress/ 153 | 154 | # DocProject is a documentation generator add-in 155 | DocProject/buildhelp/ 156 | DocProject/Help/*.HxT 157 | DocProject/Help/*.HxC 158 | DocProject/Help/*.hhc 159 | DocProject/Help/*.hhk 160 | DocProject/Help/*.hhp 161 | DocProject/Help/Html2 162 | DocProject/Help/html 163 | 164 | # Click-Once directory 165 | publish/ 166 | 167 | # Publish Web Output 168 | *.[Pp]ublish.xml 169 | *.azurePubxml 170 | # Note: Comment the next line if you want to checkin your web deploy settings, 171 | # but database connection strings (with potential passwords) will be unencrypted 172 | *.pubxml 173 | *.publishproj 174 | 175 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 176 | # checkin your Azure Web App publish settings, but sensitive information contained 177 | # in these scripts will be unencrypted 178 | PublishScripts/ 179 | 180 | # NuGet Packages 181 | *.nupkg 182 | # The packages folder can be ignored because of Package Restore 183 | **/[Pp]ackages/* 184 | # except build/, which is used as an MSBuild target. 185 | !**/[Pp]ackages/build/ 186 | # Uncomment if necessary however generally it will be regenerated when needed 187 | #!**/[Pp]ackages/repositories.config 188 | # NuGet v3's project.json files produces more ignorable files 189 | *.nuget.props 190 | *.nuget.targets 191 | 192 | # Microsoft Azure Build Output 193 | csx/ 194 | *.build.csdef 195 | 196 | # Microsoft Azure Emulator 197 | ecf/ 198 | rcf/ 199 | 200 | # Windows Store app package directories and files 201 | AppPackages/ 202 | BundleArtifacts/ 203 | Package.StoreAssociation.xml 204 | _pkginfo.txt 205 | *.appx 206 | 207 | # Visual Studio cache files 208 | # files ending in .cache can be ignored 209 | *.[Cc]ache 210 | # but keep track of directories ending in .cache 211 | !*.[Cc]ache/ 212 | 213 | # Others 214 | ClientBin/ 215 | ~$* 216 | *~ 217 | *.dbmdl 218 | *.dbproj.schemaview 219 | *.jfm 220 | *.pfx 221 | *.publishsettings 222 | orleans.codegen.cs 223 | 224 | # Including strong name files can present a security risk 225 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 226 | #*.snk 227 | 228 | # Since there are multiple workflows, uncomment next line to ignore bower_components 229 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 230 | #bower_components/ 231 | 232 | # RIA/Silverlight projects 233 | Generated_Code/ 234 | 235 | # Backup & report files from converting an old project file 236 | # to a newer Visual Studio version. Backup files are not needed, 237 | # because we have git ;-) 238 | _UpgradeReport_Files/ 239 | Backup*/ 240 | UpgradeLog*.XML 241 | UpgradeLog*.htm 242 | ServiceFabricBackup/ 243 | *.rptproj.bak 244 | 245 | # SQL Server files 246 | *.mdf 247 | *.ldf 248 | *.ndf 249 | 250 | # Business Intelligence projects 251 | *.rdl.data 252 | *.bim.layout 253 | *.bim_*.settings 254 | *.rptproj.rsuser 255 | 256 | # Microsoft Fakes 257 | FakesAssemblies/ 258 | 259 | # GhostDoc plugin setting file 260 | *.GhostDoc.xml 261 | 262 | # Node.js Tools for Visual Studio 263 | .ntvs_analysis.dat 264 | node_modules/ 265 | 266 | # Visual Studio 6 build log 267 | *.plg 268 | 269 | # Visual Studio 6 workspace options file 270 | *.opt 271 | 272 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 273 | *.vbw 274 | 275 | # Visual Studio LightSwitch build output 276 | **/*.HTMLClient/GeneratedArtifacts 277 | **/*.DesktopClient/GeneratedArtifacts 278 | **/*.DesktopClient/ModelManifest.xml 279 | **/*.Server/GeneratedArtifacts 280 | **/*.Server/ModelManifest.xml 281 | _Pvt_Extensions 282 | 283 | # Paket dependency manager 284 | .paket/paket.exe 285 | paket-files/ 286 | 287 | # FAKE - F# Make 288 | .fake/ 289 | 290 | # JetBrains Rider 291 | .idea/ 292 | *.sln.iml 293 | 294 | # CodeRush 295 | .cr/ 296 | 297 | # Python Tools for Visual Studio (PTVS) 298 | __pycache__/ 299 | *.pyc 300 | 301 | # Cake - Uncomment if you are using it 302 | # tools/** 303 | # !tools/packages.config 304 | 305 | # Tabs Studio 306 | *.tss 307 | 308 | # Telerik's JustMock configuration file 309 | *.jmconfig 310 | 311 | # BizTalk build output 312 | *.btp.cs 313 | *.btm.cs 314 | *.odx.cs 315 | *.xsd.cs 316 | 317 | # OpenCover UI analysis results 318 | OpenCover/ 319 | 320 | # Azure Stream Analytics local run output 321 | ASALocalRun/ 322 | 323 | # MSBuild Binary and Structured Log 324 | *.binlog 325 | 326 | # NVidia Nsight GPU debugger configuration file 327 | *.nvuser 328 | 329 | # MFractors (Xamarin productivity tool) working folder 330 | .mfractor/ 331 | 332 | *.DotSettings 333 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "modules/mcs-unity"] 2 | path = modules/mcs-unity 3 | url = https://github.com/aldelaro5/mcs-unity 4 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6.0 5 | 6 | 7 | https://github.com/ManlyMarco/RuntimeUnityEditor 8 | 9 | Copyright GPL-3 © 2018 10 | 11 | RuntimeUnityEditor 12 | 13 | In-game inspector and debugging tools for applications made with Unity3D game engine 14 | 15 | true 16 | true 17 | embedded 18 | 512 19 | 20 | 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | PreserveNewest 27 | 28 | 29 | 30 | 31 | 32 | 33 | $(OutputPath)\..\TEMP_COPY_$(AssemblyName) 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 59 | 60 | $(IntermediateOutputPath)GeneratedConstantsFile.cs 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Runtime Unity Editor / Debugging Tools 2 | In-game inspector, editor and interactive console for applications made with Unity3D game engine. It's designed for debugging and modding Unity games, but can also be used as a universal trainer. Runs under [BepInEx5](https://github.com/BepInEx/BepInEx), BepInEx6 IL2CPP and [UMM](https://github.com/newman55/unity-mod-manager). 3 | 4 | ## Features 5 | - Works on most games made in Unity 4.x or newer that use either the mono or IL2CPP runtime (currently IL2CPP support is in beta) 6 | - Minimal impact on the game - no GameObjects or Components are spawned (outside of the plugin component loaded by the mod loader) and no hooks are used (except if requested for profiler) 7 | - GameObject and component browser 8 | - Object inspector (allows modifying values of objects in real time) with clipboard 9 | - REPL C# console with autostart scripts 10 | - Simple Profiler 11 | - Object serialization/dumping 12 | - dnSpy integration (navigate to member in dnSpy) 13 | - Mouse inspect (find objects or UI elements by clicking with mouse) 14 | - Gizmos (Transform origin, Renderer bounds, Collider area, etc.) 15 | - All parts are integrated together (e.g. REPL console can access inspected object, inspector can focus objects on GameObject list, etc.) 16 | - Right click on most objects to bring up a context menu with more options 17 | - and many other... 18 | 19 | ![preview](https://user-images.githubusercontent.com/39247311/208912018-014154e1-7ad8-4df0-a4a3-662c334ccedc.jpg) 20 | 21 | ## How to use 22 | RUE is available for different mod loaders in separate builds. They are largely the same, although BepInEx version is the most supported and has some extra features. If you don't already have to use a specific mod loader, BepInEx is recommended. 23 | 24 | ### BepInEx (mono) 25 | 1. Install BepInEx v4.x or v5.x if you don't have it already. You can download it [here](https://github.com/BepInEx/BepInEx). 26 | 2. Download the latest BepInEx build (RuntimeUnityEditor_BepInEx5_vX.X.zip) from the [Releases](https://github.com/ManlyMarco/RuntimeUnityEditor/releases) page. Make sure to get the correct version for your BepInEx. 27 | 3. Extract the BepInEx folder from the archive directly into your game directory (you should already have a BepInEx folder there from previous step). Replace files if asked. 28 | 4. To turn on press the F12 key when in-game. A window should appear on top of the game. If it doesn't appear, check logs for errors. 29 | 30 | Note: If the plugin fails to load under BepInEx 4 with a type load exception, move RuntimeUnityEditor.Core.dll to BepInEx/core folder. 31 | 32 | ### BepInEx (IL2CPP) 33 | **WARNING:** This is a beta build that is unstable and with many features broken. It is likely to not work in many (most?) IL2CPP games, as it relies on unstripped UnityEngine.IMGUIModule.dll (and possibly other game-specific differences). It works in HoneyCome if used together with the [IMGUIModule.Il2Cpp.CoreCLR.Patcher](https://github.com/IllusionMods/BepisPlugins/releases/tag/r19.3.2) which can be used with some other games with some degree of success. 34 | 35 | IL2CPP bug reports will be largely ignored, while PRs to fix issues will be greatly appreciated. 36 | 37 | 1. If you don't have it already, install and configure BepInEx v6 (at least be.664). You can download it [here](https://github.com/BepInEx/BepInEx). 38 | 2. Download the latest RUE release for your version of BepInEx from the [Releases](https://github.com/ManlyMarco/RuntimeUnityEditor/releases) page (make sure it's the IL2CPP version). 39 | 3. Extract the release archive from the archive directly into your game directory (you should already have a BepInEx folder there from previous step). Replace files if asked. 40 | 4. To turn on press the F12 key when in-game. A window should appear on top of the game. If it doesn't appear, check game log for errors. 41 | 42 | ### Unity Mod Manager 43 | 1. Install Unity Mod Manager if you don't have it already. You can download it [here](https://www.nexusmods.com/site/mods/21). Select your game from the the dropdown list and then click the Install button. 44 | 2. Download the latest UMM build (RuntimeUnityEditor_UMM_vX.X.zip) from the [Releases](https://github.com/ManlyMarco/RuntimeUnityEditor/releases) page. 45 | 3. Goto the Mods tab in Unity Mod Manager and drag the RuntimeUnityEditor zip file into the Unity Mod Manager GUI at the bottom where it says drop zip file here. 46 | 4. To turn on press the F12 key when in-game. A window should appear on top of the game. If it doesn't appear, check logs for errors. 47 | 48 | 49 | ## Known issues 50 | - Text is invisible, scrambled, not scaled properly or otherwise looks wrong: Most likely the `Arial.ttf` font is missing from the system (Unity UI default font, may be different in some games). This can happen when running a game on Linux with [misconfigured wine](https://github.com/ManlyMarco/RuntimeUnityEditor/issues/55), and on some regional versions of Windows (e.g. Chinese). To resolve this you have to find the Arial font and install it. 51 | - The C# REPL console is only available in a subset of games. This is because mcs (the current REPL backend) does not work with `.NET Standard` runtime used by default in more recent versions of Unity (because of missing features). There is no fix, the backend will have to be changed, possibly to Lua. 52 | - The wireframe toggle might not work in some games because of an incompatible rendering setup. 53 | - The IL2CPP version is broken in `insert your game name here`. At the moment the main issue is stripped IMGUI assemblies, see https://github.com/ManlyMarco/RuntimeUnityEditor/issues/97 54 | 55 | 56 | ## How to build 57 | 1. Get Visual Studio 2019 (or later). 58 | 2. Clone the repository recursively (`git clone --recursive https://github.com/ManlyMarco/RuntimeUnityEditor`, VS 2022 can do it too). 59 | 3. Open the solution in Visual Studio and hit Build All. 60 | 61 | Notes: 62 | - If you already have the repository cloned or want to update the mcs submodule you need to run `git submodule update --init --recursive` on your local repository (if you have VS 2022 you can do it from the source control tab). 63 | - An old version of UnityEngine.dll (pre-2017) is used for compatibility with all Unity versions 4.x and above, because the new UnityEngine.dll in 2017+ forwards all of the split types into their new respective dll files. 64 | 65 | --- 66 | 67 | You can support development of my plugins through my Patreon page: https://www.patreon.com/ManlyMarco 68 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Bepin5/LogViewer/LogViewerListener.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BepInEx.Logging; 3 | using RuntimeUnityEditor.Core; 4 | 5 | namespace RuntimeUnityEditor.Bepin5.LogViewer 6 | { 7 | internal sealed class LogViewerListener : ILogListener 8 | { 9 | private readonly LogViewerWindow _owner; 10 | 11 | public LogViewerListener(LogViewerWindow owner) 12 | { 13 | _owner = owner; 14 | } 15 | 16 | public void Dispose() 17 | { 18 | 19 | } 20 | 21 | public void LogEvent(object sender, LogEventArgs eventArgs) 22 | { 23 | if ((_owner.LogLevelFilter & eventArgs.Level) == 0) 24 | return; 25 | 26 | try 27 | { 28 | _owner.OnLogEvent(sender, eventArgs); 29 | } 30 | catch (Exception e) 31 | { 32 | RuntimeUnityEditorCore.Logger.Log(Core.Utils.Abstractions.LogLevel.Error, $"[{nameof(LogViewerWindow)}] Unexpected crash when trying to parse stack trace, disabling log capture!" + e); 33 | _owner.Capture = false; 34 | } 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Bepin5/RuntimeUnityEditor.Bepin5.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net35 5 | Library 6 | 7 | ..\bin\BepInEx5\ 8 | false 9 | 10 | BepInEx\plugins\RuntimeUnityEditor 11 | 12 | 13 | 14 | true 15 | True 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | compile 29 | all 30 | 31 | 32 | 33 | 34 | 35 | 36 | compile 37 | all 38 | 39 | 40 | compile 41 | all 42 | 43 | 44 | compile 45 | all 46 | 47 | 48 | compile 49 | all 50 | 51 | 52 | compile 53 | all 54 | 55 | 56 | compile 57 | all 58 | 59 | 60 | compile 61 | all 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Bepin5/RuntimeUnityEditor5.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using BepInEx; 3 | using BepInEx.Configuration; 4 | using BepInEx.Logging; 5 | using RuntimeUnityEditor.Bepin5.LogViewer; 6 | using RuntimeUnityEditor.Core; 7 | using RuntimeUnityEditor.Core.Utils.Abstractions; 8 | using UnityEngine; 9 | 10 | namespace RuntimeUnityEditor.Bepin5 11 | { 12 | /// 13 | /// This is a loader plugin for BepInEx5. 14 | /// When referencing RuntimeUnityEditor from other code it's recommended to not reference this assembly and instead reference RuntimeUnityEditorCore directly. 15 | /// If you need your code to run after RUE is initialized, add a [BepInDependency(RuntimeUnityEditorCore.GUID)] attribute to your plugin. 16 | /// You can see if RuntimeUnityEditor has finished loading with RuntimeUnityEditorCore.IsInitialized(). 17 | /// 18 | [Obsolete("It's recommended to reference RuntimeUnityEditorCore directly")] 19 | [BepInPlugin(RuntimeUnityEditorCore.GUID, "Runtime Unity Editor", RuntimeUnityEditorCore.Version)] 20 | public class RuntimeUnityEditor5 : BaseUnityPlugin 21 | { 22 | private static RuntimeUnityEditorCore Instance { get; set; } 23 | 24 | private void Start() 25 | { 26 | if (!TomlTypeConverter.CanConvert(typeof(Rect))) 27 | { 28 | var converter = RuntimeUnityEditor.Core.Utils.TomlTypeConverter.GetConverter(typeof(Rect)); 29 | TomlTypeConverter.AddConverter(typeof(Rect), new TypeConverter { ConvertToObject = converter.ConvertToObject, ConvertToString = converter.ConvertToString }); 30 | } 31 | 32 | Instance = new RuntimeUnityEditorCore(new Bep5InitSettings(this)); 33 | 34 | Instance.AddFeature(new LogViewerWindow()); 35 | } 36 | 37 | private void Update() 38 | { 39 | Instance.Update(); 40 | } 41 | 42 | private void LateUpdate() 43 | { 44 | Instance.LateUpdate(); 45 | } 46 | 47 | private void OnGUI() 48 | { 49 | Instance.OnGUI(); 50 | } 51 | 52 | private sealed class Bep5InitSettings : InitSettings 53 | { 54 | private readonly RuntimeUnityEditor5 _instance; 55 | 56 | public Bep5InitSettings(RuntimeUnityEditor5 instance) 57 | { 58 | _instance = instance; 59 | LoggerWrapper = new Logger5(_instance.Logger); 60 | } 61 | 62 | protected override Action RegisterSetting(string category, string name, T defaultValue, string description, Action onValueUpdated) 63 | { 64 | var s = _instance.Config.Bind(category, name, defaultValue, description); 65 | s.SettingChanged += (sender, args) => onValueUpdated(s.Value); 66 | onValueUpdated(s.Value); 67 | return x => s.Value = x; 68 | } 69 | 70 | public override MonoBehaviour PluginMonoBehaviour => _instance; 71 | public override ILoggerWrapper LoggerWrapper { get; } 72 | public override string ConfigPath => Paths.ConfigPath; 73 | } 74 | 75 | private sealed class Logger5 : ILoggerWrapper 76 | { 77 | private readonly ManualLogSource _logger; 78 | 79 | public Logger5(ManualLogSource logger) 80 | { 81 | _logger = logger; 82 | } 83 | 84 | public void Log(Core.Utils.Abstractions.LogLevel logLevel, object content) 85 | { 86 | _logger.Log((BepInEx.Logging.LogLevel)logLevel, content); 87 | } 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Bepin6.IL2CPP/LogViewer/LogViewerListener.cs: -------------------------------------------------------------------------------- 1 | using BepInEx.Logging; 2 | using RuntimeUnityEditor.Core; 3 | 4 | namespace RuntimeUnityEditor.Bepin6.LogViewer 5 | { 6 | internal sealed class LogViewerListener : ILogListener 7 | { 8 | private readonly LogViewerWindow _owner; 9 | 10 | public LogViewerListener(LogViewerWindow owner) 11 | { 12 | _owner = owner; 13 | } 14 | 15 | public void Dispose() 16 | { 17 | 18 | } 19 | 20 | public void LogEvent(object sender, LogEventArgs eventArgs) 21 | { 22 | if ((_owner.LogLevelFilter & eventArgs.Level) == 0) 23 | return; 24 | 25 | try 26 | { 27 | _owner.OnLogEvent(sender, eventArgs); 28 | } 29 | catch (Exception e) 30 | { 31 | RuntimeUnityEditorCore.Logger.Log(Core.Utils.Abstractions.LogLevel.Error, $"[{nameof(LogViewerWindow)}] Unexpected crash when trying to parse stack trace, disabling log capture!" + e); 32 | _owner.Capture = false; 33 | } 34 | } 35 | 36 | public LogLevel LogLevelFilter { get; } = LogLevel.All; 37 | } 38 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Bepin6.IL2CPP/RuntimeUnityEditor.Bepin6.IL2CPP.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | Library 6 | 7 | enable 8 | disable 9 | 10 | ..\bin\BepInEx6-IL2CPP\ 11 | false 12 | 13 | BepInEx\plugins\RuntimeUnityEditor 14 | 15 | 16 | 17 | true 18 | True 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | compile 29 | all 30 | 31 | 32 | 33 | 34 | 35 | 36 | compile 37 | all 38 | 39 | 40 | compile 41 | all 42 | 43 | 44 | compile 45 | all 46 | 47 | 48 | compile 49 | all 50 | 51 | 52 | compile 53 | all 54 | 55 | 56 | compile 57 | all 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Bepin6.IL2CPP/RuntimeUnityEditorPluginIL2CPP.cs: -------------------------------------------------------------------------------- 1 | using BepInEx; 2 | using BepInEx.Configuration; 3 | using BepInEx.Logging; 4 | using BepInEx.Unity.IL2CPP; 5 | using RuntimeUnityEditor.Bepin6.LogViewer; 6 | using RuntimeUnityEditor.Core; 7 | using RuntimeUnityEditor.Core.Utils.Abstractions; 8 | using UnityEngine; 9 | 10 | namespace RuntimeUnityEditor.Bepin6.IL2CPP 11 | { 12 | /// 13 | /// This is a loader plugin for BepInEx6 (IL2CPP version). 14 | /// When referencing RuntimeUnityEditor from other code it's recommended to not reference this assembly and instead reference RuntimeUnityEditorCore directly. 15 | /// If you need your code to run after RUE is initialized, add a [BepInDependency(RuntimeUnityEditorCore.GUID)] attribute to your plugin. 16 | /// You can see if RuntimeUnityEditor has finished loading with RuntimeUnityEditorCore.IsInitialized(). 17 | /// 18 | [Obsolete("It's recommended to reference RuntimeUnityEditorCore directly")] 19 | [BepInPlugin(RuntimeUnityEditorCore.GUID, "Runtime Unity Editor", RuntimeUnityEditorCore.Version)] 20 | public class RuntimeUnityEditorPluginIL2CPP : BasePlugin 21 | { 22 | private static RuntimeUnityEditorCore _coreInstance = null!; 23 | 24 | /// 25 | public override void Load() 26 | { 27 | if (!TomlTypeConverter.CanConvert(typeof(Rect))) 28 | { 29 | var converter = Core.Utils.TomlTypeConverter.GetConverter(typeof(Rect)); 30 | TomlTypeConverter.AddConverter(typeof(Rect), new TypeConverter { ConvertToObject = converter.ConvertToObject, ConvertToString = converter.ConvertToString }); 31 | } 32 | 33 | _coreInstance = new RuntimeUnityEditorCore(new Bep6InitSettings(this)); 34 | 35 | _coreInstance.AddFeature(new LogViewerWindow()); 36 | } 37 | 38 | private class RuntimeUnityEditorHelper : MonoBehaviour 39 | { 40 | 41 | private void Update() 42 | { 43 | _coreInstance.Update(); 44 | } 45 | 46 | private void LateUpdate() 47 | { 48 | _coreInstance.LateUpdate(); 49 | } 50 | 51 | private void OnGUI() 52 | { 53 | _coreInstance.OnGUI(); 54 | } 55 | } 56 | 57 | private sealed class Bep6InitSettings : InitSettings 58 | { 59 | private readonly RuntimeUnityEditorPluginIL2CPP _instance; 60 | private readonly RuntimeUnityEditorHelper _helper; 61 | 62 | public Bep6InitSettings(RuntimeUnityEditorPluginIL2CPP instance) 63 | { 64 | _instance = instance ?? throw new ArgumentNullException(nameof(instance)); 65 | _helper = instance.AddComponent(); 66 | LoggerWrapper = new Logger6(_instance.Log); 67 | } 68 | 69 | protected override Action RegisterSetting(string category, string name, T defaultValue, string description, Action onValueUpdated) 70 | { 71 | var s = _instance.Config.Bind(category, name, defaultValue, description); 72 | s.SettingChanged += (sender, args) => onValueUpdated(s.Value); 73 | onValueUpdated(s.Value); 74 | return x => s.Value = x; 75 | } 76 | 77 | public override MonoBehaviour PluginMonoBehaviour => _helper; 78 | public override ILoggerWrapper LoggerWrapper { get; } 79 | public override string ConfigPath => Paths.ConfigPath; 80 | } 81 | 82 | private sealed class Logger6 : ILoggerWrapper 83 | { 84 | private readonly ManualLogSource _logger; 85 | 86 | public Logger6(ManualLogSource logger) 87 | { 88 | _logger = logger; 89 | } 90 | 91 | public void Log(Core.Utils.Abstractions.LogLevel logLevel, object content) 92 | { 93 | _logger.Log((BepInEx.Logging.LogLevel)logLevel, content); 94 | } 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core.IL2CPP/Libs/Unity.InputSystem.dll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManlyMarco/RuntimeUnityEditor/1cb4bc68ece99c0d0b7bdf9149831f8a59ca0dcb/RuntimeUnityEditor.Core.IL2CPP/Libs/Unity.InputSystem.dll -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core.IL2CPP/RuntimeUnityEditor.Core.IL2CPP.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0 5 | Library 6 | 7 | RuntimeUnityEditor.Core 8 | 9 | disable 10 | disable 11 | true 12 | 13 | ..\bin\Core-IL2CPP\ 14 | false 15 | 16 | $(DefineConstants);IL2CPP 17 | 18 | 19 | 20 | true 21 | True 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | Libs\Unity.InputSystem.dll 38 | False 39 | 40 | 41 | 42 | 43 | 44 | compile 45 | all 46 | 47 | 48 | compile 49 | all 50 | 51 | 52 | compile 53 | all 54 | 55 | 56 | compile 57 | all 58 | 59 | 60 | compile 61 | all 62 | 63 | 64 | compile 65 | all 66 | 67 | 68 | compile 69 | all 70 | 71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core.IL2CPP/RuntimeUnityEditor.Core.IL2CPP.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RuntimeUnityEditor.Core.IL2CPP 5 | $version$ 6 | ManlyMarco 7 | https://github.com/ManlyMarco/RuntimeUnityEditor 8 | false 9 | In-game inspector and debugging tools for applications made with Unity3D game engine (BepInEx6 IL2CPP version) 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core.Mono/RuntimeUnityEditor.Core.Mono.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net35 5 | Library 6 | 7 | RuntimeUnityEditor.Core 8 | RuntimeUnityEditor.Core 9 | 10 | ..\bin\Core-Mono\ 11 | false 12 | 13 | 14 | 15 | true 16 | True 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | compile 32 | all 33 | 34 | 35 | compile 36 | all 37 | 38 | 39 | compile 40 | all 41 | 42 | 43 | compile 44 | all 45 | 46 | 47 | compile 48 | all 49 | 50 | 51 | compile 52 | all 53 | 54 | 55 | compile 56 | all 57 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core.Mono/RuntimeUnityEditor.Core.Mono.nuspec: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RuntimeUnityEditor.Core 5 | $version$ 6 | ManlyMarco 7 | https://github.com/ManlyMarco/RuntimeUnityEditor 8 | false 9 | In-game inspector and debugging tools for applications made with Unity3D game engine 10 | true 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core.Mono/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("RuntimeUnityEditor.Bepin5")] 4 | [assembly: InternalsVisibleTo("RuntimeUnityEditor.Bepin6.IL2CPP")] 5 | [assembly: InternalsVisibleTo("RuntimeUnityEditor.UMM")] 6 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Features/CursorUnlocker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using RuntimeUnityEditor.Core.Utils.Abstractions; 4 | using UnityEngine; 5 | 6 | #pragma warning disable CS1591 7 | 8 | namespace RuntimeUnityEditor.Core 9 | { 10 | /// 11 | /// Feature that forces the mouse cursor to stay unlocked/visible. Might cause issues with some games, especially FPSes. 12 | /// 13 | public sealed class CursorUnlocker : FeatureBase 14 | { 15 | private bool _obsoleteCursor; 16 | 17 | private PropertyInfo _curLockState; 18 | private PropertyInfo _curVisible; 19 | 20 | private int _previousCursorLockState; 21 | private bool _previousCursorVisible; 22 | 23 | protected override void Initialize(InitSettings initSettings) 24 | { 25 | // Reflection for compatibility with Unity 4.x 26 | var tCursor = typeof(Cursor); 27 | 28 | _curLockState = tCursor.GetProperty("lockState", BindingFlags.Static | BindingFlags.Public); 29 | _curVisible = tCursor.GetProperty("visible", BindingFlags.Static | BindingFlags.Public); 30 | 31 | if (_curLockState == null || _curVisible == null) 32 | { 33 | _obsoleteCursor = true; 34 | 35 | _curLockState = typeof(Screen).GetProperty("lockCursor", BindingFlags.Static | BindingFlags.Public); 36 | _curVisible = typeof(Screen).GetProperty("showCursor", BindingFlags.Static | BindingFlags.Public); 37 | 38 | if (_curLockState == null || _curVisible == null) 39 | throw new InvalidOperationException("Unsupported Cursor class"); 40 | } 41 | 42 | DisplayName = "Unlock cursor"; 43 | Enabled = true; 44 | DisplayType = FeatureDisplayType.Hidden; 45 | } 46 | 47 | protected override void Update() 48 | { 49 | if (_obsoleteCursor) 50 | _curLockState.SetValue(null, false, null); 51 | else 52 | _curLockState.SetValue(null, 0, null); 53 | 54 | _curVisible.SetValue(null, true, null); 55 | } 56 | 57 | protected override void LateUpdate() 58 | { 59 | if (_obsoleteCursor) 60 | _curLockState.SetValue(null, false, null); 61 | else 62 | _curLockState.SetValue(null, 0, null); 63 | 64 | _curVisible.SetValue(null, true, null); 65 | } 66 | 67 | protected override void OnGUI() 68 | { 69 | if (_obsoleteCursor) 70 | _curLockState.SetValue(null, false, null); 71 | else 72 | _curLockState.SetValue(null, 0, null); 73 | 74 | _curVisible.SetValue(null, true, null); 75 | } 76 | 77 | protected override void VisibleChanged(bool visible) 78 | { 79 | if (visible) 80 | { 81 | _previousCursorLockState = _obsoleteCursor ? Convert.ToInt32((bool)_curLockState.GetValue(null, null)) : (int)_curLockState.GetValue(null, null); 82 | _previousCursorVisible = (bool)_curVisible.GetValue(null, null); 83 | } 84 | else 85 | { 86 | if (!_previousCursorVisible || _previousCursorLockState != 0) 87 | { 88 | if (_obsoleteCursor) 89 | _curLockState.SetValue(null, Convert.ToBoolean(_previousCursorLockState), null); 90 | else 91 | _curLockState.SetValue(null, _previousCursorLockState, null); 92 | 93 | _curVisible.SetValue(null, _previousCursorVisible, null); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Features/Gizmos/lib/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace RuntimeUnityEditor.Core.Gizmos.lib 2 | { 3 | internal class Constants 4 | { 5 | public const string UniqueIdentifier = "Popcron.Gizmos"; 6 | public const string EnabledKey = UniqueIdentifier + ".Enabled"; 7 | } 8 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Features/Gizmos/lib/Drawer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using RuntimeUnityEditor.Core.Gizmos.lib.Drawers; 4 | using UnityEngine; 5 | 6 | namespace RuntimeUnityEditor.Core.Gizmos.lib 7 | { 8 | internal abstract class Drawer 9 | { 10 | private static Dictionary typeToDrawer = null; 11 | 12 | public abstract int Draw(ref Vector3[] buffer, params object[] args); 13 | 14 | public Drawer() 15 | { 16 | 17 | } 18 | 19 | public static Drawer Get() where T : class 20 | { 21 | //find all drawers 22 | if (typeToDrawer == null) 23 | { 24 | typeToDrawer = new Dictionary(); 25 | 26 | //add defaults 27 | typeToDrawer.Add(typeof(CubeDrawer), new CubeDrawer()); 28 | typeToDrawer.Add(typeof(LineDrawer), new LineDrawer()); 29 | typeToDrawer.Add(typeof(PolygonDrawer), new PolygonDrawer()); 30 | typeToDrawer.Add(typeof(SquareDrawer), new SquareDrawer()); 31 | typeToDrawer.Add(typeof(ArcDrawer), new ArcDrawer()); 32 | 33 | ////find extras 34 | //var types = AccessTools.AllTypes(); 35 | //foreach (Type type in types) 36 | //{ 37 | // if (type.IsAbstract) 38 | // { 39 | // continue; 40 | // } 41 | // 42 | // if (type.IsSubclassOf(typeof(Drawer)) && !typeToDrawer.ContainsKey(type)) 43 | // { 44 | // try 45 | // { 46 | // Drawer value = (Drawer)Activator.CreateInstance(type); 47 | // typeToDrawer[type] = value; 48 | // } 49 | // catch (Exception e) 50 | // { 51 | // Debug.LogError($"couldnt register drawer of type {type} because {e.Message}"); 52 | // } 53 | // } 54 | //} 55 | } 56 | 57 | if (typeToDrawer.TryGetValue(typeof(T), out Drawer drawer)) 58 | { 59 | return drawer; 60 | } 61 | else 62 | { 63 | return null; 64 | } 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Features/Gizmos/lib/Drawers/ArcDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RuntimeUnityEditor.Core.Gizmos.lib.Drawers 4 | { 5 | internal class ArcDrawer : Drawer 6 | { 7 | public ArcDrawer() 8 | { 9 | 10 | } 11 | 12 | public override int Draw(ref Vector3[] buffer, params object[] values) 13 | { 14 | Vector3 position = (Vector3)values[0]; 15 | int points = (int)values[1]; 16 | float radius = (float)values[2]; 17 | float offset = (float)values[3]; 18 | Quaternion rotation = (Quaternion)values[4]; 19 | var angle = (float)values[5]; 20 | 21 | float step = 360f / points; 22 | offset *= Mathf.Deg2Rad; 23 | 24 | for (int i = 0; i < points; i++) 25 | { 26 | float cx = Mathf.Cos(Mathf.Deg2Rad * step * i + offset) * radius; 27 | float cy = Mathf.Sin(Mathf.Deg2Rad * step * i + offset) * radius; 28 | Vector3 current = new Vector3(cx, cy); 29 | 30 | float nx = Mathf.Cos(Mathf.Deg2Rad * step * (i + 1) + offset) * radius; 31 | float ny = Mathf.Sin(Mathf.Deg2Rad * step * (i + 1) + offset) * radius; 32 | Vector3 next = new Vector3(nx, ny); 33 | 34 | buffer[i * 2] = position + (rotation * current); 35 | buffer[(i * 2) + 1] = position + (rotation * next); 36 | 37 | if ((angle -= step) <= 0) break; 38 | } 39 | 40 | return points * 2; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Features/Gizmos/lib/Drawers/CubeDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RuntimeUnityEditor.Core.Gizmos.lib.Drawers 4 | { 5 | internal class CubeDrawer : Drawer 6 | { 7 | public CubeDrawer() 8 | { 9 | 10 | } 11 | 12 | public override int Draw(ref Vector3[] buffer, params object[] values) 13 | { 14 | Vector3 position = (Vector3)values[0]; 15 | Quaternion rotation = (Quaternion)values[1]; 16 | Vector3 size = (Vector3)values[2]; 17 | 18 | size *= 0.5f; 19 | 20 | Vector3 point1 = new Vector3(position.x - size.x, position.y - size.y, position.z - size.z); 21 | Vector3 point2 = new Vector3(position.x + size.x, position.y - size.y, position.z - size.z); 22 | Vector3 point3 = new Vector3(position.x + size.x, position.y + size.y, position.z - size.z); 23 | Vector3 point4 = new Vector3(position.x - size.x, position.y + size.y, position.z - size.z); 24 | 25 | Vector3 point5 = new Vector3(position.x - size.x, position.y - size.y, position.z + size.z); 26 | Vector3 point6 = new Vector3(position.x + size.x, position.y - size.y, position.z + size.z); 27 | Vector3 point7 = new Vector3(position.x + size.x, position.y + size.y, position.z + size.z); 28 | Vector3 point8 = new Vector3(position.x - size.x, position.y + size.y, position.z + size.z); 29 | 30 | point1 = rotation * (point1 - position); 31 | point1 += position; 32 | 33 | point2 = rotation * (point2 - position); 34 | point2 += position; 35 | 36 | point3 = rotation * (point3 - position); 37 | point3 += position; 38 | 39 | point4 = rotation * (point4 - position); 40 | point4 += position; 41 | 42 | point5 = rotation * (point5 - position); 43 | point5 += position; 44 | 45 | point6 = rotation * (point6 - position); 46 | point6 += position; 47 | 48 | point7 = rotation * (point7 - position); 49 | point7 += position; 50 | 51 | point8 = rotation * (point8 - position); 52 | point8 += position; 53 | 54 | //square 55 | buffer[0] = point1; 56 | buffer[1] = point2; 57 | 58 | buffer[2] = point2; 59 | buffer[3] = point3; 60 | 61 | buffer[4] = point3; 62 | buffer[5] = point4; 63 | 64 | buffer[6] = point4; 65 | buffer[7] = point1; 66 | 67 | //other square 68 | buffer[8] = point5; 69 | buffer[9] = point6; 70 | 71 | buffer[10] = point6; 72 | buffer[11] = point7; 73 | 74 | buffer[12] = point7; 75 | buffer[13] = point8; 76 | 77 | buffer[14] = point8; 78 | buffer[15] = point5; 79 | 80 | //connectors 81 | buffer[16] = point1; 82 | buffer[17] = point5; 83 | 84 | buffer[18] = point2; 85 | buffer[19] = point6; 86 | 87 | buffer[20] = point3; 88 | buffer[21] = point7; 89 | 90 | buffer[22] = point4; 91 | buffer[23] = point8; 92 | 93 | return 24; 94 | } 95 | } 96 | } 97 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Features/Gizmos/lib/Drawers/LineDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RuntimeUnityEditor.Core.Gizmos.lib.Drawers 4 | { 5 | internal class LineDrawer : Drawer 6 | { 7 | public LineDrawer() 8 | { 9 | 10 | } 11 | 12 | public override int Draw(ref Vector3[] buffer, params object[] args) 13 | { 14 | buffer[0] = (Vector3)args[0]; 15 | buffer[1] = (Vector3)args[1]; 16 | return 2; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Features/Gizmos/lib/Drawers/PolygonDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RuntimeUnityEditor.Core.Gizmos.lib.Drawers 4 | { 5 | internal class PolygonDrawer : Drawer 6 | { 7 | public PolygonDrawer() 8 | { 9 | 10 | } 11 | 12 | public override int Draw(ref Vector3[] buffer, params object[] values) 13 | { 14 | Vector3 position = (Vector3)values[0]; 15 | int points = (int)values[1]; 16 | float radius = (float)values[2]; 17 | float offset = (float)values[3]; 18 | Quaternion rotation = (Quaternion)values[4]; 19 | 20 | float step = 360f / points; 21 | offset *= Mathf.Deg2Rad; 22 | 23 | for (int i = 0; i < points; i++) 24 | { 25 | float cx = Mathf.Cos(Mathf.Deg2Rad * step * i + offset) * radius; 26 | float cy = Mathf.Sin(Mathf.Deg2Rad * step * i + offset) * radius; 27 | Vector3 current = new Vector3(cx, cy); 28 | 29 | float nx = Mathf.Cos(Mathf.Deg2Rad * step * (i + 1) + offset) * radius; 30 | float ny = Mathf.Sin(Mathf.Deg2Rad * step * (i + 1) + offset) * radius; 31 | Vector3 next = new Vector3(nx, ny); 32 | 33 | buffer[i * 2] = position + (rotation * current); 34 | buffer[(i * 2) + 1] = position + (rotation * next); 35 | } 36 | 37 | return points * 2; 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Features/Gizmos/lib/Drawers/SquareDrawer.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RuntimeUnityEditor.Core.Gizmos.lib.Drawers 4 | { 5 | internal class SquareDrawer : Drawer 6 | { 7 | public SquareDrawer() 8 | { 9 | 10 | } 11 | 12 | public override int Draw(ref Vector3[] buffer, params object[] values) 13 | { 14 | Vector2 position = default; 15 | if (values[0] is Vector2 p2) 16 | { 17 | position = p2; 18 | } 19 | else if (values[0] is Vector3 p3) 20 | { 21 | position = p3; 22 | } 23 | 24 | Quaternion rotation = (Quaternion)values[1]; 25 | 26 | Vector2 size = default; 27 | if (values[2] is Vector2 s2) 28 | { 29 | size = s2; 30 | } 31 | else if (values[2] is Vector3 s3) 32 | { 33 | size = s3; 34 | } 35 | 36 | size *= 0.5f; 37 | 38 | Vector2 point1 = new Vector3(position.x - size.x, position.y - size.y); 39 | Vector2 point2 = new Vector3(position.x + size.x, position.y - size.y); 40 | Vector2 point3 = new Vector3(position.x + size.x, position.y + size.y); 41 | Vector2 point4 = new Vector3(position.x - size.x, position.y + size.y); 42 | 43 | point1 = rotation * (point1 - position); 44 | point1 += position; 45 | 46 | point2 = rotation * (point2 - position); 47 | point2 += position; 48 | 49 | point3 = rotation * (point3 - position); 50 | point3 += position; 51 | 52 | point4 = rotation * (point4 - position); 53 | point4 += position; 54 | 55 | //square 56 | buffer[0] = point1; 57 | buffer[1] = point2; 58 | 59 | buffer[2] = point2; 60 | buffer[3] = point3; 61 | 62 | buffer[4] = point3; 63 | buffer[5] = point4; 64 | 65 | //loop back to start 66 | buffer[6] = point4; 67 | buffer[7] = point1; 68 | 69 | return 8; 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Features/Gizmos/lib/Element.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RuntimeUnityEditor.Core.Gizmos.lib 4 | { 5 | internal class Element 6 | { 7 | public Vector3[] points = { }; 8 | public Color color = Color.white; 9 | public bool dashed = false; 10 | } 11 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Features/MouseInspect.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Text; 4 | using RuntimeUnityEditor.Core.ObjectTree; 5 | using RuntimeUnityEditor.Core.Utils; 6 | using RuntimeUnityEditor.Core.Utils.Abstractions; 7 | using UnityEngine; 8 | 9 | #pragma warning disable CS1591 10 | 11 | namespace RuntimeUnityEditor.Core 12 | { 13 | /// 14 | /// Feature that shows information about GameObjects and Canvas elements located directly under the mouse cursor. 15 | /// 16 | public class MouseInspect : FeatureBase 17 | { 18 | private static readonly StringBuilder _hoverTextSb = new StringBuilder(100); 19 | private static string _hoverText = string.Empty; 20 | private GUIStyle _labelSkin; 21 | 22 | protected override void Initialize(InitSettings initSettings) 23 | { 24 | DisplayName = "Mouse inspect"; 25 | } 26 | 27 | protected override void Update() 28 | { 29 | var camera = Camera.main; 30 | var raycastHit = DoRaycast(camera); 31 | var canvasHits = DoCanvas(camera); 32 | 33 | var selectedTransform = ObjectTreeViewer.Instance.SelectedTransform; 34 | 35 | _hoverTextSb.Length = 0; 36 | 37 | if (raycastHit != null) 38 | { 39 | _hoverTextSb.AppendFormat("Raycast hit:\n{0}0: {1} pos={2}\n", selectedTransform == raycastHit ? "> " : "", raycastHit.name, raycastHit.position); 40 | } 41 | 42 | if (canvasHits.Count > 0) 43 | { 44 | _hoverTextSb.AppendLine("Canvas hits:"); 45 | for (var index = 0; index < canvasHits.Count && index < 10; index++) 46 | { 47 | var hit = canvasHits[index]; 48 | _hoverTextSb.AppendFormat("{0}{1}: {2} pos={3} size={4}\n", selectedTransform == hit ? "> " : "", index + 1, hit.name, hit.position, hit.sizeDelta); 49 | } 50 | } 51 | 52 | if (_hoverTextSb.Length > 0) 53 | { 54 | if (ObjectTreeViewer.Initialized) 55 | { 56 | _hoverTextSb.Append("[ Press Middle Mouse Button to browse to the next object ]"); 57 | if (UnityInput.Current.GetMouseButtonDown(2)) 58 | { 59 | var all = Enumerable.Repeat(raycastHit, 1).Concat(canvasHits.Cast()).Where(x => x != null).ToList(); 60 | 61 | var currentIndex = all.IndexOf(selectedTransform); 62 | // If nothing is selected -1 + 1 = 0 so start from 0 63 | var nextObject = all.Concat(all).Skip(currentIndex + 1).First(); 64 | 65 | ObjectTreeViewer.Instance.SelectAndShowObject(nextObject); 66 | } 67 | } 68 | 69 | _hoverText = _hoverTextSb.ToString(); 70 | } 71 | else 72 | { 73 | _hoverText = string.Empty; 74 | } 75 | } 76 | 77 | protected override void OnGUI() 78 | { 79 | if (_hoverText.Length > 0) 80 | { 81 | if (_labelSkin == null) _labelSkin = GUI.skin.label.CreateCopy(); 82 | 83 | // Figure out which corner of the screen to draw the hover text in 84 | _labelSkin.alignment = TextAnchor.UpperLeft; 85 | 86 | var pos = UnityInput.Current.mousePosition; 87 | var displayRect = new Rect((int)pos.x + 5, Screen.height - (int)pos.y + 20, (int)(Screen.width / 2), 500); 88 | 89 | if ((int)displayRect.x > Screen.width / 2) 90 | { 91 | displayRect.x = (int)(displayRect.x - displayRect.width); 92 | _labelSkin.alignment = TextAnchor.UpperRight; 93 | } 94 | 95 | if ((int)displayRect.y > Screen.height / 2) 96 | { 97 | displayRect.y = (int)(displayRect.y - (displayRect.height + 23)); 98 | // Upper -> Lower 99 | _labelSkin.alignment += TextAnchor.LowerRight - TextAnchor.UpperRight; 100 | } 101 | 102 | IMGUIUtils.DrawLabelWithOutline(displayRect, _hoverText, _labelSkin, Color.white, Color.black, 2); 103 | } 104 | } 105 | 106 | private static Transform DoRaycast(Camera camera) 107 | { 108 | if (camera == null) return null; 109 | // Based on https://github.com/sinai-dev/CppExplorer/blob/master/src/Menu/InspectUnderMouse.cs (MIT License) 110 | var ray = camera.ScreenPointToRay(UnityInput.Current.mousePosition); 111 | return Physics.Raycast(ray, out RaycastHit hit, 1000f) ? hit.transform : null; 112 | } 113 | 114 | private static List DoCanvas(Camera camera) 115 | { 116 | Vector2 mousePosition = UnityInput.Current.mousePosition; 117 | 118 | var hits = Object.FindObjectsOfType().Where(rt => 119 | { 120 | // Only RTs that are visible on screen 121 | if (rt && rt.sizeDelta.x > 0 && rt.sizeDelta.y > 0 && rt.gameObject.activeInHierarchy) 122 | { 123 | var canvas = rt.GetComponentInParent(); 124 | if (canvas != null && canvas.enabled && rt.GetComponentsInParent().All(x => x.alpha > 0.1f)) 125 | { 126 | // Figure out what camera this canvas is drawn to, overlay draws to a hidden camera that's accessed by a null 127 | var cam = canvas.renderMode == RenderMode.ScreenSpaceOverlay ? null : canvas.worldCamera != null ? canvas.worldCamera : camera; 128 | return RectTransformUtility.RectangleContainsScreenPoint(rt, mousePosition, cam); 129 | } 130 | } 131 | return false; 132 | }); 133 | 134 | // Smallest rect first since generally UI elements get progressively smaller inside each other, and we mostly care about buttons which are the smallest 135 | return hits.OrderBy(x => x.sizeDelta.sqrMagnitude).ToList(); 136 | } 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Features/WireframeFeature.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using RuntimeUnityEditor.Core.Utils.Abstractions; 5 | using UnityEngine; 6 | 7 | #pragma warning disable CS1591 8 | 9 | namespace RuntimeUnityEditor.Core 10 | { 11 | /// 12 | /// Feature that turns on Unity's built-in wireframe mode. 13 | /// 14 | public sealed class WireframeFeature : FeatureBase 15 | { 16 | // Effects incompatible with wireframes 17 | private static readonly string[] _disableEffectNames = { "GlobalFog", "BloomAndFlares", "CustomRender", "AmplifyColorEffect", "PostProcessLayer" }; 18 | 19 | private MonoBehaviour _monoBehaviour; 20 | 21 | private readonly List _disabledEffects = new List(); 22 | private static Camera _targetCamera; 23 | private bool _updateCoRunning; 24 | 25 | protected override void Initialize(InitSettings initSettings) 26 | { 27 | #if !IL2CPP 28 | UnityFeatureHelper.EnsureCameraRenderEventsAreAvailable(); 29 | #endif 30 | 31 | DisplayName = "Wireframe"; 32 | Enabled = false; 33 | 34 | _monoBehaviour = initSettings.PluginMonoBehaviour; 35 | } 36 | 37 | public override bool Enabled 38 | { 39 | get => base.Enabled; 40 | set 41 | { 42 | if (base.Enabled != value) 43 | { 44 | base.Enabled = value; 45 | 46 | if (value) 47 | _monoBehaviour.AbstractStartCoroutine(UpdateCo()); 48 | } 49 | } 50 | } 51 | 52 | private IEnumerator UpdateCo() 53 | { 54 | // Prevent race condition when rapidly toggling Enabled 55 | if(_updateCoRunning) 56 | yield break; 57 | 58 | _updateCoRunning = true; 59 | 60 | CollectEffects(); 61 | SetEffectEnabled(false); 62 | 63 | yield return null; 64 | 65 | // Need to wait for multiple frames for some effects to be disabled 66 | if (Enabled) 67 | yield return null; 68 | 69 | if (Enabled) 70 | { 71 | Camera.onPreRender += (Camera.CameraCallback)OnPreRender; 72 | Camera.onPostRender += (Camera.CameraCallback)OnPostRender; 73 | 74 | yield return null; 75 | 76 | while (Enabled) 77 | { 78 | // Turn effects off every frame in case they are re-enabled 79 | SetEffectEnabled(false); 80 | yield return null; 81 | } 82 | 83 | Camera.onPreRender -= (Camera.CameraCallback)OnPreRender; 84 | Camera.onPostRender -= (Camera.CameraCallback)OnPostRender; 85 | } 86 | 87 | SetEffectEnabled(true); 88 | 89 | _updateCoRunning = false; 90 | } 91 | 92 | private void CollectEffects() 93 | { 94 | _disabledEffects.Clear(); 95 | // Find all cameras and their problematic effects that are currently enabled 96 | _disabledEffects.AddRange(Object.FindObjectsOfType() 97 | .SelectMany(cam => _disableEffectNames.Select(cam.GetComponent)) 98 | .OfType() 99 | .Where(b => b && b.enabled)); 100 | } 101 | 102 | private void SetEffectEnabled(bool enabled) 103 | { 104 | for (var i = 0; i < _disabledEffects.Count; i++) 105 | { 106 | var effect = _disabledEffects[i]; 107 | if (effect) 108 | effect.enabled = enabled; 109 | } 110 | } 111 | 112 | private static void OnPreRender(Camera cam) 113 | { 114 | if (GL.wireframe) return; 115 | 116 | _targetCamera = cam; 117 | GL.wireframe = true; 118 | } 119 | 120 | private static void OnPostRender(Camera cam) 121 | { 122 | if (_targetCamera == cam) 123 | { 124 | GL.wireframe = false; 125 | _targetCamera = null; 126 | } 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/RuntimeUnityEditor.Core.shproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 39cc0f9c-3dfa-4542-a680-a564f3089777 5 | 14.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/ScreenPartition.cs: -------------------------------------------------------------------------------- 1 | namespace RuntimeUnityEditor.Core 2 | { 3 | /// 4 | /// Sections of the screen managed by RUE's window manager. 5 | /// 6 | public enum ScreenPartition 7 | { 8 | /// 9 | /// Let RUE choose, behavior can change in future versions. 10 | /// 11 | Default = 0, 12 | /// 13 | /// Span the entire screen, minus the taskbar. 14 | /// 15 | Full = 5, 16 | /// 17 | /// Span the center of the screen, top to bottom, minus the taskbar. 18 | /// 19 | Center = 10, 20 | /// 21 | /// Upper half of screen center. It's larger than the lower part. 22 | /// 23 | CenterUpper = 11, 24 | /// 25 | /// Lower half of screen center. It's smaller than the upper part. 26 | /// 27 | CenterLower = 12, 28 | /// 29 | /// Span the left part of the screen, top to bottom, minus the taskbar. 30 | /// 31 | Left = 20, 32 | /// 33 | /// Upper half of the screen's left side. Not necessarily equal in size to the lower half. 34 | /// 35 | LeftUpper = 21, 36 | /// 37 | /// Lower half of the screen's left side. Not necessarily equal in size to the upper half. 38 | /// 39 | LeftLower = 22, 40 | /// 41 | /// Span the right part of the screen, top to bottom, minus the taskbar. 42 | /// 43 | Right = 30, 44 | /// 45 | /// Upper half of the screen's right side. Not necessarily equal in size to the lower half. 46 | /// 47 | RightUpper = 31, 48 | /// 49 | /// Lower half of the screen's right side. Not necessarily equal in size to the upper half. 50 | /// 51 | RightLower = 32, 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/Abstractions/AssetBundleManagerHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Linq; 4 | using System.Reflection; 5 | using UnityEngine; 6 | 7 | namespace RuntimeUnityEditor.Core.Utils.Abstractions 8 | { 9 | internal static class AssetBundleManagerHelper 10 | { 11 | private static readonly IDictionary _abCacheManifestDic; 12 | private static readonly MethodInfo _abCacheUnloadMethod; 13 | 14 | static AssetBundleManagerHelper() 15 | { 16 | try 17 | { 18 | var abmType = AppDomain.CurrentDomain.GetAssemblies().SelectMany(x => x.GetTypesSafe()).FirstOrDefault(x => x.Name == "AssetBundleManager"); 19 | if (abmType != null) 20 | { 21 | // public static Dictionary ManifestBundlePack {get;} 22 | var mbpProp = abmType.GetProperty("ManifestBundlePack", BindingFlags.Static | BindingFlags.Public); 23 | _abCacheManifestDic = mbpProp?.GetValue(null, null) as IDictionary; 24 | 25 | if (_abCacheManifestDic != null) 26 | { 27 | // public static void UnloadAssetBundle(string assetBundleName, bool isUnloadForceRefCount, string manifestAssetBundleName = null, bool unloadAllLoadedObjects = false) 28 | _abCacheUnloadMethod = abmType.GetMethod("UnloadAssetBundle", BindingFlags.Static | BindingFlags.Public, null, new[] { typeof(string), typeof(bool), typeof(string), typeof(bool) }, 29 | null); 30 | } 31 | } 32 | } 33 | catch (Exception ex) 34 | { 35 | Debug.LogError(ex.Message); 36 | } 37 | } 38 | 39 | public static void DrawButtonIfAvailable() 40 | { 41 | if (_abCacheUnloadMethod != null && GUILayout.Button("Clear Bundle Cache")) 42 | { 43 | try 44 | { 45 | var unloadedCount = ClearAssetBundleCache(); 46 | RuntimeUnityEditorCore.Logger.Log(LogLevel.Message, "Unloaded " + unloadedCount + " AssetBundles"); 47 | } 48 | catch (Exception e) 49 | { 50 | RuntimeUnityEditorCore.Logger.Log(LogLevel.Message | LogLevel.Error, "Failed to clear the AssetBundle cache - " + e); 51 | } 52 | } 53 | } 54 | 55 | private static int ClearAssetBundleCache() 56 | { 57 | var unloadedCount = 0; 58 | 59 | var manifestDicEnumerator = _abCacheManifestDic.GetEnumerator(); 60 | while (manifestDicEnumerator.MoveNext()) 61 | { 62 | var valueType = manifestDicEnumerator.Value.GetType(); 63 | // public Dictionary LoadedAssetBundles {get; set;} 64 | var loadedBundlesProp = valueType.GetProperty("LoadedAssetBundles", BindingFlags.Instance | BindingFlags.Public); 65 | var loadedBundlesDic = loadedBundlesProp?.GetValue(manifestDicEnumerator.Value, null) as IDictionary; 66 | 67 | if (loadedBundlesDic == null) throw new InvalidOperationException("Failed to get LoadedAssetBundles dictionary"); 68 | 69 | // Need a copy of keys because unloading removes them 70 | foreach (var labsKey in loadedBundlesDic.Keys.Cast().ToList()) 71 | { 72 | _abCacheUnloadMethod.Invoke(null, new[] { labsKey, true, manifestDicEnumerator.Key, false }); 73 | unloadedCount++; 74 | } 75 | } 76 | 77 | return unloadedCount; 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/Abstractions/DataUtility.cs: -------------------------------------------------------------------------------- 1 | // ColorUtility and JsonUtility replacements because 4.x Unity does not have them 2 | 3 | using System.Globalization; 4 | using UnityEngine; 5 | 6 | namespace RuntimeUnityEditor.Core.Utils.Abstractions 7 | { 8 | /// 9 | /// Utilities related to the and classes. 10 | /// 11 | public static class ColorUtility 12 | { 13 | private static bool DoTryParseHtmlColor(string htmlString, out Color32 result) 14 | { 15 | result = new Color32(255, 255, 255, 255); 16 | 17 | try 18 | { 19 | if (htmlString.IndexOf('#') != -1) 20 | htmlString = htmlString.Replace("#", ""); 21 | 22 | var r = byte.Parse(htmlString.Substring(0, 2), NumberStyles.AllowHexSpecifier); 23 | var g = byte.Parse(htmlString.Substring(2, 2), NumberStyles.AllowHexSpecifier); 24 | var b = byte.Parse(htmlString.Substring(4, 2), NumberStyles.AllowHexSpecifier); 25 | var a = byte.Parse(htmlString.Substring(6, 2), NumberStyles.AllowHexSpecifier); 26 | 27 | result = new Color32(r, g, b, a); 28 | } 29 | catch 30 | { 31 | return false; 32 | } 33 | 34 | return true; 35 | } 36 | 37 | /// 38 | /// Tries to parse a string into a . The string should be in the format of an RGBA hex color code, e.g. #FF0000FF or A0FF05FF. 39 | /// 40 | public static bool TryParseHtmlString(string htmlString, out Color color) 41 | { 42 | var ret = DoTryParseHtmlColor(htmlString, out var c); 43 | color = c; 44 | 45 | return ret; 46 | } 47 | 48 | /// 49 | /// Converts a to a string in the format of an RGBA hex color code, e.g. #FF0000FF. 50 | /// 51 | public static string ToHtmlStringRGBA(Color color) 52 | { 53 | var col32 = new Color32( 54 | (byte)Mathf.Clamp(Mathf.RoundToInt(color.r * 255), 0, 255), 55 | (byte)Mathf.Clamp(Mathf.RoundToInt(color.g * 255), 0, 255), 56 | (byte)Mathf.Clamp(Mathf.RoundToInt(color.b * 255), 0, 255), 57 | (byte)Mathf.Clamp(Mathf.RoundToInt(color.a * 255), 0, 255)); 58 | 59 | return $"{col32.r:X2}{col32.g:X2}{col32.b:X2}{col32.a:X2}"; 60 | } 61 | } 62 | 63 | /// 64 | /// Utilities for turning things into readable strings. 65 | /// 66 | public static class ToStringUtility 67 | { 68 | /// 69 | /// Converts a Vector to a string representation. 70 | /// 71 | public static string ObjectToString(Vector2 obj) => $"{obj.x} {obj.y}"; 72 | 73 | /// 74 | /// Converts a Vector to a string representation. 75 | /// 76 | public static string ObjectToString(Vector3 obj) => $"{obj.x} {obj.y} {obj.z}"; 77 | 78 | /// 79 | /// Converts a Vector to a string representation. 80 | /// 81 | public static string ObjectToString(Vector4 obj) => $"{obj.x} {obj.y} {obj.z}, {obj.w}"; 82 | 83 | /// 84 | /// Converts a Quaternion to a string representation. 85 | /// 86 | public static string ObjectToString(Quaternion obj) => $"{obj.x} {obj.y} {obj.z}, {obj.w}"; 87 | 88 | /// 89 | /// Converts a string representation back to a Vector object. 90 | /// 91 | public static Vector2 StringToVector2(string str) 92 | { 93 | var array = str.Split(' '); 94 | return new Vector2(float.Parse(array[0]), float.Parse(array[1])); 95 | } 96 | 97 | /// 98 | /// Converts a string representation back to a Vector object. 99 | /// 100 | public static Vector3 StringToVector3(string str) 101 | { 102 | var array = str.Split(' '); 103 | return new Vector3(float.Parse(array[0]), float.Parse(array[1]), float.Parse(array[2])); 104 | } 105 | 106 | /// 107 | /// Converts a string representation back to a Vector object. 108 | /// 109 | public static Vector4 StringToVector4(string str) 110 | { 111 | var array = str.Split(' '); 112 | return new Vector4(float.Parse(array[0]), float.Parse(array[1]), float.Parse(array[2]), float.Parse(array[3])); 113 | } 114 | 115 | /// 116 | /// Converts a string representation back to a Quaternion object. 117 | /// 118 | public static Quaternion StringToQuaternion(string str) 119 | { 120 | var array = str.Split(' '); 121 | return new Quaternion(float.Parse(array[0]), float.Parse(array[1]), float.Parse(array[2]), float.Parse(array[3])); 122 | } 123 | } 124 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/Abstractions/IL2CppAbstractions.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Linq; 3 | using UnityEngine; 4 | #if IL2CPP 5 | using Il2CppInterop.Runtime.Injection; 6 | #endif 7 | 8 | namespace RuntimeUnityEditor.Core 9 | { 10 | internal static class IL2CppAbstractions 11 | { 12 | public static void Set(this RectOffset obj, int left, int right, int top, int bottom) 13 | { 14 | obj.left = left; 15 | obj.right = right; 16 | obj.top = top; 17 | obj.bottom = bottom; 18 | } 19 | 20 | public static Transform[] AbstractGetChildren(this Transform transform, bool castedIl2Cpp = true) 21 | { 22 | var children = new Transform[transform.childCount]; 23 | for (var i = 0; i < transform.childCount; i++) 24 | { 25 | var child = transform.GetChild(i); 26 | #if IL2CPP 27 | children[i] = castedIl2Cpp ? child.TryAutoCast() as Transform ?? child : child; 28 | #else 29 | children[i] = child; 30 | #endif 31 | } 32 | return children; 33 | } 34 | 35 | public static Component[] AbstractGetAllComponents(this GameObject gameObject, bool castedIl2Cpp = true) 36 | { 37 | var result = (Component[])gameObject.GetComponents(); 38 | #if IL2CPP 39 | if (castedIl2Cpp) 40 | result = result.Select(x => IL2CppExtensions.TryAutoCast(x) ?? x).OfType().ToArray(); 41 | #endif 42 | return result; 43 | } 44 | 45 | public static Component[] AbstractGetAllComponents(this Component component, bool castedIl2Cpp = true) 46 | { 47 | var result = (Component[])component.GetComponents(); 48 | #if IL2CPP 49 | if (castedIl2Cpp) 50 | result = result.Select(x => x.TryAutoCast() ?? x).OfType().ToArray(); 51 | #endif 52 | return result; 53 | } 54 | 55 | public static Component[] AbstractGetAllComponentsInChildren(this GameObject gameObject, bool includeInactive, bool castedIl2Cpp = true) 56 | { 57 | var result = (Component[])gameObject.GetComponentsInChildren(includeInactive); 58 | #if IL2CPP 59 | if (castedIl2Cpp) 60 | result = result.Select(x => x.TryAutoCast() ?? x).OfType().ToArray(); 61 | #endif 62 | return result; 63 | } 64 | 65 | public static Component[] AbstractGetAllComponentsInChildren(this Component component, bool includeInactive, bool castedIl2Cpp = true) 66 | { 67 | var result = (Component[])component.GetComponentsInChildren(includeInactive); 68 | #if IL2CPP 69 | if (castedIl2Cpp) 70 | result = result.Select(x => x.TryAutoCast() ?? x).OfType().ToArray(); 71 | #endif 72 | return result; 73 | } 74 | 75 | public static Coroutine AbstractStartCoroutine(this MonoBehaviour monoBehaviour, IEnumerator routine) 76 | { 77 | #if IL2CPP 78 | // TODO Remove all direct dependencies on BepInEx 79 | return monoBehaviour.StartCoroutine(BepInEx.Unity.IL2CPP.Utils.Collections.CollectionExtensions.WrapToIl2Cpp(routine)); 80 | #else 81 | return monoBehaviour.StartCoroutine(routine); 82 | #endif 83 | } 84 | 85 | public static T AbstractAddComponent(this GameObject go) where T : Component 86 | { 87 | #if IL2CPP 88 | var t = typeof(T); 89 | if (!ClassInjector.IsTypeRegisteredInIl2Cpp(t)) 90 | ClassInjector.RegisterTypeInIl2Cpp(t); 91 | return go.AddComponent(Il2CppInterop.Runtime.Il2CppType.From(t)).Cast(); 92 | #else 93 | return go.AddComponent(); 94 | #endif 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/Abstractions/IL2CppExtensions.cs: -------------------------------------------------------------------------------- 1 | #if IL2CPP 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Reflection; 7 | using BepInEx.Unity.IL2CPP; 8 | using HarmonyLib; 9 | using Il2CppInterop.Runtime.InteropTypes; 10 | using RuntimeUnityEditor.Core.Utils; 11 | using RuntimeUnityEditor.Core.Utils.Abstractions; 12 | 13 | namespace RuntimeUnityEditor.Core 14 | { 15 | internal static class IL2CppExtensions 16 | { 17 | public static object TryAutoCast(this Il2CppSystem.Object component) 18 | { 19 | if (component == null) throw new ArgumentNullException(nameof(component)); 20 | 21 | try 22 | { 23 | var il2CppType = component.GetIl2CppType(); 24 | 25 | var monoType = TryGetMonoType(il2CppType); 26 | if (monoType != null) 27 | return _mIl2CppObjectBaseCast.MakeGenericMethod(monoType).Invoke(component, null); 28 | } 29 | catch (Exception e) 30 | { 31 | RuntimeUnityEditorCore.Logger.Log(LogLevel.Warning, $"TryAutoCast failed for type {component.GetType().FullName}: {e}"); 32 | } 33 | 34 | return null; 35 | } 36 | 37 | private static readonly MethodInfo _mIl2CppObjectBaseCast = _mIl2CppObjectBaseCast = AccessTools.Method(typeof(Il2CppObjectBase), nameof(Il2CppObjectBase.Cast)); 38 | private static readonly Dictionary _cachedTypes = new Dictionary(); 39 | private static readonly HashSet _cachedAssemblies = new HashSet(); 40 | 41 | public static Type TryGetMonoType(this Il2CppSystem.Type il2CppType) 42 | { 43 | var typeName = il2CppType.FullName; 44 | 45 | if (_cachedTypes.TryGetValue(typeName, out var monoType)) return monoType; 46 | 47 | // Only mono interop types that are statically referenced are automatically loaded 48 | // Since RUE is entirely using reflection, we sometimes need to manually load the interop assemblies 49 | if (!_cachedAssemblies.Contains(il2CppType.Assembly.FullName)) 50 | { 51 | try 52 | { 53 | // TODO Remove all direct dependencies on BepInEx 54 | // Only available since bleeding edge build #680 55 | var interopAssPath = AccessTools.PropertyGetter(typeof(IL2CPPChainloader).Assembly.GetType("BepInEx.Unity.IL2CPP.Il2CppInteropManager"), "IL2CPPInteropAssemblyPath")?.Invoke(null, null) as string; 56 | if (interopAssPath == null) throw new InvalidOperationException("Interop assembly path is not available"); 57 | 58 | var shortAssName = il2CppType.Assembly.GetName().Name; 59 | 60 | var assPath = Path.Combine(interopAssPath, shortAssName + ".dll"); 61 | if (File.Exists(assPath)) 62 | { 63 | RuntimeUnityEditorCore.Logger.Log(LogLevel.Info, "Loading interop assembly from " + assPath); 64 | Assembly.LoadFile(assPath); 65 | } 66 | } 67 | catch (Exception e) 68 | { 69 | Console.WriteLine(e); 70 | } 71 | } 72 | 73 | // Look for newly loaded assemblies and add all types from them to the cache 74 | var newAssemblies = AccessTools.AllAssemblies().Where(x => x.FullName != null && _cachedAssemblies.Add(x.FullName)).ToList(); 75 | //Console.WriteLine(string.Join("\n", newAssemblies.First(x=>x.FullName.Contains("ConfigurationM")).GetTypesSafe().Select(x=>x.FullName))); 76 | foreach (var types in newAssemblies /*.Where(x => x.FullName != "InjectedMonoTypes")*/ 77 | .SelectMany(x => x.GetTypesSafe()) 78 | .GroupBy(x => x.FullName)) 79 | { 80 | _cachedTypes[types.Key] = types.OrderByDescending(a => 81 | { 82 | var assemblyFullName = a.Assembly.FullName; 83 | return assemblyFullName.StartsWith("UnityEngine.") || assemblyFullName.StartsWith("System."); 84 | }).First(); 85 | } 86 | 87 | if (_cachedTypes.TryGetValue(typeName, out monoType)) return monoType; 88 | 89 | // If we still can't find the type, try to find it by name and namespace. Nested types are missing the namespace part in IL2CPP Types. 90 | // todo try this first to be safe? 91 | var typeNameWithNamespace = il2CppType.Namespace + "." + typeName.Replace('.', '+'); 92 | 93 | //Console.WriteLine(typeName); 94 | //Console.WriteLine(il2CppType.FormatTypeName()); 95 | //Console.WriteLine(il2CppType.Namespace); 96 | 97 | if (_cachedTypes.TryGetValue(typeNameWithNamespace, out monoType)) 98 | { 99 | _cachedTypes[typeName] = monoType; 100 | _cachedTypes[typeNameWithNamespace] = monoType; 101 | return monoType; 102 | } 103 | else 104 | { 105 | RuntimeUnityEditorCore.Logger.Log(LogLevel.Warning, "Failed to find any Mono Type matching an IL2CPP Type: " + il2CppType.AssemblyQualifiedName); 106 | _cachedTypes[typeName] = null; 107 | return null; 108 | } 109 | } 110 | } 111 | } 112 | #endif 113 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/Abstractions/ILoggerWrapper.cs: -------------------------------------------------------------------------------- 1 | namespace RuntimeUnityEditor.Core.Utils.Abstractions 2 | { 3 | /// 4 | /// A place for RUE to write logs to. 5 | /// 6 | public interface ILoggerWrapper 7 | { 8 | /// 9 | /// Write a log message (source is always RUE). 10 | /// 11 | /// How important the message is. 12 | /// Content of the message, can be any type so it will have to be converted to string if given log level is shown. 13 | void Log(LogLevel logLevel, object content); 14 | } 15 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/Abstractions/InitSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | 5 | namespace RuntimeUnityEditor.Core.Utils.Abstractions 6 | { 7 | /// 8 | /// Collection of things required for RUE to be successfully initialized. 9 | /// 10 | public abstract class InitSettings 11 | { 12 | #region API 13 | 14 | /// 15 | /// Register a new persistent setting. 16 | /// 17 | /// Type of the setting 18 | /// Used for grouping 19 | /// Name/Key 20 | /// Initial value if setting was never changed 21 | /// What the setting does 22 | /// Called when the setting changes, and immediately after this method finishes (with either the default value or the previously stored value). 23 | /// An Action that can be used to set the setting's value 24 | protected abstract Action RegisterSetting(string category, string name, T defaultValue, string description, Action onValueUpdated); 25 | /// 26 | /// Instance MB of the plugin 27 | /// 28 | public abstract MonoBehaviour PluginMonoBehaviour { get; } 29 | /// 30 | /// Log output 31 | /// 32 | public abstract ILoggerWrapper LoggerWrapper { get; } 33 | /// 34 | /// Path to write/read extra config files from 35 | /// 36 | public abstract string ConfigPath { get; } 37 | 38 | #endregion 39 | 40 | #region Helpers 41 | 42 | /// 43 | /// Wrapper for a setting. 44 | /// 45 | public abstract class SettingBase 46 | { 47 | private static readonly List _events; 48 | private static readonly System.Timers.Timer _timer; 49 | static SettingBase() 50 | { 51 | _events = new List(2); 52 | _timer = new System.Timers.Timer(700); 53 | _timer.AutoReset = false; 54 | _timer.Stop(); 55 | _timer.Elapsed += (sender, e) => 56 | { 57 | lock (_events) 58 | { 59 | foreach (var setting in _events) 60 | setting.OnValueChanged(); 61 | _events.Clear(); 62 | } 63 | }; 64 | } 65 | 66 | /// 67 | /// Queue a setting to be written back to the config system. 68 | /// 69 | protected static void QueueValueChanged(SettingBase setting) 70 | { 71 | lock (_events) 72 | { 73 | if (!_events.Contains(setting)) 74 | _events.Add(setting); 75 | _timer.Stop(); 76 | _timer.Start(); 77 | } 78 | } 79 | 80 | /// 81 | /// Called when the value of the setting changes. 82 | /// 83 | protected abstract void OnValueChanged(); 84 | } 85 | 86 | /// 87 | public sealed class Setting : SettingBase 88 | { 89 | private T _value; 90 | /// 91 | /// Triggered when changes. 92 | /// 93 | public event Action ValueChanged; 94 | 95 | /// 96 | /// Current value of the setting. 97 | /// 98 | public T Value 99 | { 100 | get => _value; 101 | set 102 | { 103 | if (!Equals(_value, value)) 104 | { 105 | _value = value; 106 | QueueValueChanged(this); 107 | } 108 | } 109 | } 110 | 111 | /// 112 | protected override void OnValueChanged() 113 | { 114 | ValueChanged?.Invoke(_value); 115 | } 116 | } 117 | /// 118 | public Setting RegisterSetting(string category, string name, T defaultValue, string description) 119 | { 120 | var setting = new Setting(); 121 | var callback = RegisterSetting(category, name, defaultValue, description, obj => setting.Value = obj); 122 | setting.ValueChanged += callback; 123 | return setting; 124 | } 125 | 126 | #endregion 127 | } 128 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/Abstractions/LogLevel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RuntimeUnityEditor.Core.Utils.Abstractions 4 | { 5 | /// 6 | /// How a log message should be treated. 7 | /// A single log message can have multiple levels attached to it (usually Message + something else). 8 | /// Same as BepInEx5 LogLevel. 9 | /// 10 | [Flags] 11 | public enum LogLevel 12 | { 13 | /// 14 | /// This shouldn't happen. 15 | /// 16 | None = 0, 17 | /// 18 | /// The world is burning. 19 | /// 20 | Fatal = 1, 21 | /// 22 | /// Something bad and unexpected happened. 23 | /// 24 | Error = 2, 25 | /// 26 | /// Something bad but expected or safe to ignore happened. 27 | /// 28 | Warning = 4, 29 | /// 30 | /// The user should see this, ideally in the UI. 31 | /// 32 | Message = 8, 33 | /// 34 | /// Writing home about the cool things I did. 35 | /// 36 | Info = 16, 37 | /// 38 | /// Info useful mostly for debugging RUE itself. 39 | /// 40 | Debug = 32, 41 | /// 42 | /// All for one. 43 | /// 44 | All = Debug | Info | Message | Warning | Error | Fatal, 45 | } 46 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/ComboBox.cs: -------------------------------------------------------------------------------- 1 | // Popup list created by Eric Haines 2 | // ComboBox Extended by Hyungseok Seo.(Jerry) sdragoon@nate.com 3 | // this oop version of ComboBox is refactored by zhujiangbo jumbozhu@gmail.com 4 | // Modified by MarC0 / ManlyMarco 5 | 6 | using System; 7 | using UnityEngine; 8 | 9 | namespace RuntimeUnityEditor.Core.Utils 10 | { 11 | internal class ComboBox 12 | { 13 | private static bool forceToUnShow; 14 | private static int useControlID = -1; 15 | private readonly string boxStyle; 16 | private readonly string buttonStyle; 17 | private bool isClickedComboButton; 18 | private readonly GUIContent[] listContent; 19 | private readonly GUIStyle listStyle; 20 | private readonly int _windowYmax; 21 | 22 | public ComboBox(Rect rect, GUIContent buttonContent, GUIContent[] listContent, GUIStyle listStyle, float windowYmax) 23 | { 24 | Rect = rect; 25 | ButtonContent = buttonContent; 26 | this.listContent = listContent; 27 | buttonStyle = "button"; 28 | boxStyle = "box"; 29 | this.listStyle = listStyle; 30 | _windowYmax = (int)windowYmax; 31 | } 32 | 33 | public ComboBox(Rect rect, GUIContent buttonContent, GUIContent[] listContent, string buttonStyle, 34 | string boxStyle, GUIStyle listStyle) 35 | { 36 | Rect = rect; 37 | ButtonContent = buttonContent; 38 | this.listContent = listContent; 39 | this.buttonStyle = buttonStyle; 40 | this.boxStyle = boxStyle; 41 | this.listStyle = listStyle; 42 | } 43 | 44 | public Rect Rect { get; set; } 45 | 46 | public GUIContent ButtonContent { get; set; } 47 | 48 | public void Show(Action onItemSelected) 49 | { 50 | if (forceToUnShow) 51 | { 52 | forceToUnShow = false; 53 | isClickedComboButton = false; 54 | } 55 | 56 | var done = false; 57 | var controlID = GUIUtility.GetControlID(FocusType.Passive); 58 | 59 | Vector2 currentMousePosition = Vector2.zero; 60 | if (Event.current.GetTypeForControl(controlID) == EventType.MouseUp) 61 | { 62 | if (isClickedComboButton) 63 | { 64 | done = true; 65 | currentMousePosition = Event.current.mousePosition; 66 | } 67 | } 68 | 69 | if (GUI.Button(Rect, ButtonContent, buttonStyle)) 70 | { 71 | if (useControlID == -1) 72 | { 73 | useControlID = controlID; 74 | isClickedComboButton = false; 75 | } 76 | 77 | if (useControlID != controlID) 78 | { 79 | forceToUnShow = true; 80 | useControlID = controlID; 81 | } 82 | isClickedComboButton = true; 83 | } 84 | 85 | if (isClickedComboButton) 86 | { 87 | GUI.enabled = false; 88 | GUI.color = new Color(1, 1, 1, 2); 89 | 90 | var location = GUIUtility.GUIToScreenPoint(new Vector2(Rect.x, Rect.y + listStyle.CalcHeight(listContent[0], 1.0f))); 91 | var size = new Vector2(Rect.width, listStyle.CalcHeight(listContent[0], 1.0f) * listContent.Length); 92 | 93 | var innerRect = new Rect(0, 0, size.x, size.y); 94 | 95 | var outerRectScreen = new Rect(location.x, location.y, size.x, size.y); 96 | if (outerRectScreen.yMax > _windowYmax) 97 | { 98 | outerRectScreen.height = _windowYmax - outerRectScreen.y; 99 | outerRectScreen.width += 20; 100 | } 101 | 102 | if (currentMousePosition != Vector2.zero && outerRectScreen.Contains(GUIUtility.GUIToScreenPoint(currentMousePosition))) 103 | done = false; 104 | 105 | CurrentDropdownDrawer = () => 106 | { 107 | GUI.enabled = true; 108 | 109 | var outerRectLocal = GUIUtility.ScreenToGUIRect(outerRectScreen); 110 | 111 | GUI.Box(outerRectLocal, GUIContent.none, GUI.skin.box); 112 | 113 | _scrollPosition = GUI.BeginScrollView(outerRectLocal, _scrollPosition, innerRect, false, false); 114 | { 115 | const int initialSelectedItem = -1; 116 | var newSelectedItemIndex = GUI.SelectionGrid(innerRect, initialSelectedItem, listContent, 1, listStyle); 117 | if (newSelectedItemIndex != initialSelectedItem) 118 | { 119 | onItemSelected(newSelectedItemIndex); 120 | isClickedComboButton = false; 121 | } 122 | } 123 | GUI.EndScrollView(true); 124 | }; 125 | } 126 | 127 | if (done) 128 | isClickedComboButton = false; 129 | } 130 | 131 | private Vector2 _scrollPosition = Vector2.zero; 132 | public static Action CurrentDropdownDrawer { get; set; } 133 | } 134 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/MovingAverage.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace RuntimeUnityEditor.Core.Utils 4 | { 5 | internal class MovingAverage 6 | { 7 | private readonly int _windowSize; 8 | private readonly Queue _samples; 9 | private long _sampleAccumulator; 10 | 11 | public MovingAverage(int windowSize = 11) 12 | { 13 | _windowSize = windowSize; 14 | _samples = new Queue(_windowSize + 1); 15 | } 16 | 17 | ///// 18 | ///// Highest sample value ever, even if the sample is no longer counted in the average. 19 | ///// 20 | //public long PeakValue { get; private set; } 21 | 22 | public long GetAverage() 23 | { 24 | if (_samples.Count == 0) 25 | return 0; 26 | 27 | return _sampleAccumulator / _samples.Count; 28 | } 29 | 30 | public void Sample(long newSample) 31 | { 32 | _sampleAccumulator += newSample; 33 | _samples.Enqueue(newSample); 34 | 35 | if (_samples.Count > _windowSize) 36 | _sampleAccumulator -= _samples.Dequeue(); 37 | 38 | //if (PeakValue < newSample) 39 | // PeakValue = newSample; 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/ObjectDumper/ObjectDumperExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Globalization; 3 | using System.IO; 4 | using System.Text; 5 | 6 | namespace RuntimeUnityEditor.Core.Utils.ObjectDumper 7 | { 8 | /// 9 | /// Extensions for dumping objects for debugging purposes. 10 | /// 11 | public static class ObjectDumperExtensions 12 | { 13 | /// 14 | /// Dumps the object to the console. 15 | /// 16 | public static T DumpToConsole(this T value, string name) 17 | { 18 | if (IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); 19 | 20 | using (var debugWriter = new DebugWriter()) value.Dump(name, debugWriter); 21 | 22 | return value; 23 | } 24 | 25 | /// 26 | /// Dumps the object to a file. 27 | /// 28 | public static T DumpToFile(this T value, string name, string filename) 29 | { 30 | return value.DumpToFile(filename, name, Encoding.Default); 31 | } 32 | 33 | /// 34 | /// Dumps the object to a TextWriter. 35 | /// 36 | public static T Dump(this T value, string name, TextWriter writer) 37 | { 38 | if (IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); 39 | if (writer == null) throw new ArgumentNullException(nameof(writer)); 40 | 41 | Dumper.Dump(value, name, writer); 42 | 43 | return value; 44 | } 45 | 46 | /// 47 | /// Dumps the object to a file with a specified encoding. 48 | /// 49 | public static T DumpToFile(this T value, string name, string filename, Encoding encoding) 50 | { 51 | if (IsNullOrWhiteSpace(name)) throw new ArgumentNullException(nameof(name)); 52 | if (IsNullOrWhiteSpace(filename)) throw new ArgumentNullException(nameof(filename)); 53 | if (encoding == null) throw new ArgumentNullException(nameof(encoding)); 54 | 55 | using (var streamWriter = new StreamWriter(filename, false, encoding)) value.Dump(name, streamWriter); 56 | 57 | return value; 58 | } 59 | 60 | /// 61 | /// Dumps the object to a string. 62 | /// 63 | public static string DumpToString(this T value, string name) 64 | { 65 | using (var stringWriter = new StringWriter(CultureInfo.InvariantCulture)) 66 | { 67 | value.Dump(name, stringWriter); 68 | return stringWriter.ToString(); 69 | } 70 | } 71 | 72 | internal static bool IsNullOrWhiteSpace(string value) 73 | { 74 | return value == null || value.Trim().Length == 0; 75 | } 76 | 77 | private sealed class DebugWriter : TextWriter 78 | { 79 | public DebugWriter() : base(CultureInfo.InvariantCulture) { } 80 | public override Encoding Encoding => Console.OutputEncoding; 81 | 82 | public override void Write(char value) 83 | { 84 | Console.WriteLine(value); 85 | } 86 | 87 | public override void Write(string value) 88 | { 89 | Console.WriteLine(value); 90 | } 91 | } 92 | } 93 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/OrderedSet.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | 5 | namespace RuntimeUnityEditor.Core.ObjectTree 6 | { 7 | /// 8 | /// Based on OrderedSet from answer https://stackoverflow.com/a/17853085 by AndreasHassing and George Mamaladze 9 | /// 10 | /// 11 | internal class OrderedSet : ICollection 12 | { 13 | private readonly IDictionary> _mDictionary; 14 | private readonly LinkedList _mLinkedList; 15 | 16 | public OrderedSet() 17 | : this(EqualityComparer.Default) 18 | { 19 | } 20 | 21 | public OrderedSet(IEqualityComparer comparer) 22 | { 23 | _mDictionary = new Dictionary>(comparer); 24 | _mLinkedList = new LinkedList(); 25 | } 26 | 27 | public int Count => _mDictionary.Count; 28 | 29 | public virtual bool IsReadOnly => _mDictionary.IsReadOnly; 30 | 31 | void ICollection.Add(T item) 32 | { 33 | AddLast(item); 34 | } 35 | 36 | public bool AddLast(T item) 37 | { 38 | if (_mDictionary.ContainsKey(item)) return false; 39 | 40 | var node = _mLinkedList.AddLast(item); 41 | _mDictionary.Add(item, node); 42 | return true; 43 | } 44 | 45 | public bool InsertSorted(T item, IComparer comparer) 46 | { 47 | if (_mDictionary.ContainsKey(item)) return false; 48 | 49 | var currentNode = _mLinkedList.First; 50 | while (currentNode != null) 51 | { 52 | if (comparer.Compare(currentNode.Value, item) >= 0) 53 | { 54 | _mLinkedList.AddBefore(currentNode, item); 55 | break; 56 | } 57 | currentNode = currentNode.Next; 58 | } 59 | 60 | if (currentNode == null) 61 | currentNode = _mLinkedList.AddLast(item); 62 | 63 | _mDictionary.Add(item, currentNode); 64 | return true; 65 | } 66 | 67 | public void Clear() 68 | { 69 | _mLinkedList.Clear(); 70 | _mDictionary.Clear(); 71 | } 72 | 73 | public bool Remove(T item) 74 | { 75 | if (item == null) return false; 76 | var found = _mDictionary.TryGetValue(item, out var node); 77 | if (!found) return false; 78 | _mDictionary.Remove(item); 79 | _mLinkedList.Remove(node); 80 | return true; 81 | } 82 | 83 | public IEnumerator GetEnumerator() 84 | { 85 | return _mLinkedList.GetEnumerator(); 86 | } 87 | 88 | IEnumerator IEnumerable.GetEnumerator() 89 | { 90 | return GetEnumerator(); 91 | } 92 | 93 | public bool Contains(T item) 94 | { 95 | return item != null && _mDictionary.ContainsKey(item); 96 | } 97 | 98 | public void CopyTo(T[] array, int arrayIndex) 99 | { 100 | _mLinkedList.CopyTo(array, arrayIndex); 101 | } 102 | 103 | public void RemoveAll(Predicate func) 104 | { 105 | var currentNode = _mLinkedList.First; 106 | while (currentNode != null) 107 | { 108 | var nextNode = currentNode.Next; 109 | if (func(currentNode.Value)) 110 | { 111 | _mDictionary.Remove(currentNode.Value); 112 | _mLinkedList.Remove(currentNode); 113 | } 114 | currentNode = nextNode; 115 | } 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/ReflectionUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Text; 6 | using HarmonyLib; 7 | using RuntimeUnityEditor.Core.Clipboard; 8 | using RuntimeUnityEditor.Core.Utils.Abstractions; 9 | using UnityEngine.Events; 10 | 11 | namespace RuntimeUnityEditor.Core.Utils 12 | { 13 | /// 14 | /// Utility class for reflection operations. 15 | /// 16 | public static class ReflectionUtils 17 | { 18 | /// 19 | /// Sets the value of a member (field or property) on an object. 20 | /// 21 | public static void SetValue(MemberInfo member, object obj, object value) 22 | { 23 | switch (member.MemberType) 24 | { 25 | case MemberTypes.Field: 26 | ((FieldInfo)member).SetValue(obj, value); 27 | break; 28 | case MemberTypes.Property: 29 | ((PropertyInfo)member).SetValue(obj, value, null); 30 | break; 31 | } 32 | } 33 | 34 | /// 35 | /// Gets the value of a member (field or property) on an object. 36 | /// 37 | public static object GetValue(MemberInfo member, object obj) 38 | { 39 | switch (member.MemberType) 40 | { 41 | case MemberTypes.Field: 42 | return ((FieldInfo)member).GetValue(obj); 43 | case MemberTypes.Property: 44 | return ((PropertyInfo)member).GetValue(obj, null); 45 | default: 46 | return null; 47 | } 48 | } 49 | 50 | /// 51 | /// Gets the details of a UnityEventBase object, including the target and method information. 52 | /// 53 | public static string GetEventDetails(UnityEventBase eventObj) 54 | { 55 | var mList = new List>(); 56 | for (var i = 0; i < eventObj.GetPersistentEventCount(); ++i) 57 | { 58 | // It's fine to use ? here because GetType works fine on disposed objects and we want to know the type name 59 | var name = eventObj.GetPersistentMethodName(i); 60 | var target = eventObj.GetPersistentTarget(i); 61 | var m = target?.GetType() 62 | .GetMethods(BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic) 63 | .FirstOrDefault(info => info.Name == name); 64 | if (m != null) mList.Add(new KeyValuePair(target, m)); 65 | } 66 | 67 | try 68 | { 69 | var calls = eventObj.GetPrivateExplicit("m_Calls").GetPrivate("m_RuntimeCalls").CastToEnumerable(); 70 | foreach (var call in calls) 71 | { 72 | if (call.GetPrivate("Delegate") is Delegate d) 73 | mList.Add(new KeyValuePair(d.Target, d.Method)); 74 | } 75 | } 76 | catch (Exception e) 77 | { 78 | RuntimeUnityEditorCore.Logger.Log(LogLevel.Error, e); 79 | } 80 | 81 | var sb = new StringBuilder(); 82 | 83 | foreach (var kvp in mList) 84 | { 85 | sb.Append(kvp.Key.GetType().GetSourceCodeRepresentation()); 86 | // todo make this more powerful somehow, still doesn't show much, maybe with cecil? 87 | var locals = kvp.Value.GetMethodBody()?.LocalVariables.Select(x => x.ToString()); 88 | if (locals != null) sb.Append(" - " + string.Join("; ", locals.ToArray())); 89 | sb.AppendLine(); 90 | } 91 | 92 | return sb.ToString(); 93 | } 94 | 95 | /// 96 | /// Generates a string representation of a method call, including the instance, method name, and parameters. 97 | /// 98 | public static string MethodCallToSourceRepresentation(object instance, MethodBase methodInfo, ICollection parameterStrings) 99 | { 100 | var generics = methodInfo.GetGenericArgumentsSafe(); 101 | var genericsString = generics.Length == 0 ? "" : "<" + string.Join(", ", generics.Select(x => x.FullDescription()).ToArray()) + ">"; 102 | return $"{instance?.GetType().FullDescription() ?? methodInfo.DeclaringType?.FullDescription() ?? ""}.{methodInfo.Name}{genericsString}({string.Join(", ", Enumerable.ToArray(ClipboardWindow.ResolveMethodParameters(parameterStrings)))})"; 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/ResourceUtils.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | 6 | namespace RuntimeUnityEditor.Core.Utils 7 | { 8 | /// 9 | /// Utility methods for working with embedded resources. 10 | /// 11 | public static class ResourceUtils 12 | { 13 | /// 14 | /// Read all bytes starting at current position and ending at the end of the stream. 15 | /// 16 | public static byte[] ReadAllBytes(this Stream input) 17 | { 18 | var buffer = new byte[16 * 1024]; 19 | using (var ms = new MemoryStream()) 20 | { 21 | int read; 22 | while ((read = input.Read(buffer, 0, buffer.Length)) > 0) 23 | ms.Write(buffer, 0, read); 24 | return ms.ToArray(); 25 | } 26 | } 27 | 28 | /// 29 | /// Get a file set as "Embedded Resource" from the assembly that is calling this code, or optionally from a specified assembly. 30 | /// The filename is matched to the end of the resource path, no need to give the full path. 31 | /// If 0 or more than 1 resources match the provided filename, an exception is thrown. 32 | /// For example if you have a file "ProjectRoot\Resources\icon.png" set as "Embedded Resource", you can use this to load it by 33 | /// doing GetEmbeddedResource("icon.png"), assuming that no other embedded files have the same name. 34 | /// 35 | public static byte[] GetEmbeddedResource(string resourceFileName, Assembly containingAssembly = null) 36 | { 37 | if (containingAssembly == null) 38 | containingAssembly = Assembly.GetCallingAssembly(); 39 | 40 | var resourceName = containingAssembly.GetManifestResourceNames().Single(str => str.EndsWith(resourceFileName)); 41 | 42 | using (var stream = containingAssembly.GetManifestResourceStream(resourceName)) 43 | return ReadAllBytes(stream ?? throw new InvalidOperationException($"The resource {resourceFileName} was not found")); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/ThreadingExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Threading; 6 | using UnityEngine; 7 | 8 | namespace RuntimeUnityEditor.Core.Utils 9 | { 10 | /// 11 | /// Convenience extensions for utilizing multiple threads. 12 | /// 13 | public static class ThreadingExtensions 14 | { 15 | /// 16 | public static IEnumerable RunParallel(this IEnumerable data, Func work, int workerCount = -1) 17 | { 18 | foreach (var result in RunParallel(data.ToList(), work)) 19 | yield return result; 20 | } 21 | 22 | /// 23 | /// Apply a function to a collection of data by spreading the work on multiple threads. 24 | /// Outputs of the functions are returned to the current thread and yielded one by one. 25 | /// 26 | /// Type of the input values. 27 | /// Type of the output values. 28 | /// Input values for the work function. 29 | /// Function to apply to the data on multiple threads at once. 30 | /// Number of worker threads. By default SystemInfo.processorCount is used. 31 | /// An exception was thrown inside one of the threads, and the operation was aborted. 32 | /// Need at least 1 workerCount. 33 | public static IEnumerable RunParallel(this IList data, Func work, int workerCount = -1) 34 | { 35 | if (workerCount < 0) 36 | workerCount = Mathf.Max(2, Environment.ProcessorCount); 37 | else if (workerCount == 0) 38 | throw new ArgumentException("Need at least 1 worker", nameof(workerCount)); 39 | 40 | var perThreadCount = Mathf.CeilToInt(data.Count / (float)workerCount); 41 | var doneCount = 0; 42 | 43 | var lockObj = new object(); 44 | var are = new ManualResetEvent(false); 45 | IEnumerable doneItems = null; 46 | Exception exceptionThrown = null; 47 | 48 | // Start threads to process the data 49 | for (var i = 0; i < workerCount; i++) 50 | { 51 | int first = i * perThreadCount; 52 | int last = Mathf.Min(first + perThreadCount, data.Count); 53 | ThreadPool.QueueUserWorkItem( 54 | _ => 55 | { 56 | var results = new List(perThreadCount); 57 | 58 | try 59 | { 60 | for (int dataIndex = first; dataIndex < last; dataIndex++) 61 | { 62 | if (exceptionThrown != null) break; 63 | results.Add(work(data[dataIndex])); 64 | } 65 | } 66 | catch (Exception ex) 67 | { 68 | exceptionThrown = ex; 69 | } 70 | 71 | lock (lockObj) 72 | { 73 | doneItems = doneItems == null ? results : results.Concat(doneItems); 74 | doneCount++; 75 | are.Set(); 76 | } 77 | }); 78 | } 79 | 80 | // Main thread waits for results and returns them until all threads finish 81 | while (true) 82 | { 83 | are.WaitOne(); 84 | 85 | IEnumerable toOutput; 86 | bool isDone; 87 | lock (lockObj) 88 | { 89 | toOutput = doneItems; 90 | doneItems = null; 91 | isDone = doneCount == workerCount; 92 | } 93 | 94 | if (toOutput != null) 95 | { 96 | foreach (var doneItem in toOutput) 97 | yield return doneItem; 98 | } 99 | 100 | if (isDone) 101 | break; 102 | } 103 | 104 | if (exceptionThrown != null) 105 | throw new TargetInvocationException("An exception was thrown inside one of the threads", exceptionThrown); 106 | } 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/UI/InterfaceMaker.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RuntimeUnityEditor.Core.Utils; 3 | using RuntimeUnityEditor.Core.Utils.Abstractions; 4 | using UnityEngine; 5 | using Object = UnityEngine.Object; 6 | 7 | namespace RuntimeUnityEditor.Core.UI 8 | { 9 | /// 10 | /// Handles the IMGUI skin and such. 11 | /// 12 | public static class InterfaceMaker 13 | { 14 | // These all need to be held as static properties, including textures, to prevent UnloadUnusedAssets from destroying them 15 | private static Texture2D _boxBackground; 16 | private static Texture2D _winBackground; 17 | private static GUISkin _customSkin; 18 | 19 | /// 20 | /// If mouse is inside of a given IMGUI screen rect, eat the input. 21 | /// 22 | public static void EatInputInRect(Rect eatRect) 23 | { 24 | var mousePos = UnityInput.Current.mousePosition; 25 | if (eatRect.Contains(new Vector2(mousePos.x, Screen.height - mousePos.y))) 26 | UnityInput.Current.ResetInputAxes(); 27 | } 28 | 29 | /// 30 | /// IMGUI skin used by RUE. Can be used by other plugins to get the same look and feel, just don't modify its contents. 31 | /// 32 | public static GUISkin CustomSkin 33 | { 34 | get 35 | { 36 | if (_customSkin == null) 37 | { 38 | try 39 | { 40 | _customSkin = CreateSkin(); 41 | } 42 | catch (Exception ex) 43 | { 44 | RuntimeUnityEditorCore.Logger.Log(LogLevel.Warning, "Could not load custom GUISkin - " + ex.Message); 45 | _customSkin = GUI.skin; 46 | } 47 | } 48 | 49 | return _customSkin; 50 | } 51 | } 52 | 53 | private static GUISkin CreateSkin() 54 | { 55 | var newSkin = UnityFeatureHelper.InstantiateUnityObject(GUI.skin); 56 | Object.DontDestroyOnLoad(newSkin); 57 | 58 | // Load the custom skin from resources 59 | var texData = ResourceUtils.GetEmbeddedResource("guisharp-box.png"); 60 | _boxBackground = UnityFeatureHelper.LoadTexture(texData); 61 | Object.DontDestroyOnLoad(_boxBackground); 62 | newSkin.box.onNormal.background = null; 63 | newSkin.box.normal.background = _boxBackground; 64 | newSkin.box.normal.textColor = Color.white; 65 | 66 | texData = ResourceUtils.GetEmbeddedResource("guisharp-window.png"); 67 | _winBackground = UnityFeatureHelper.LoadTexture(texData); 68 | Object.DontDestroyOnLoad(_winBackground); 69 | newSkin.window.onNormal.background = null; 70 | newSkin.window.normal.background = _winBackground; 71 | 72 | newSkin.window.padding.Set(6, 6, 22, 6); 73 | newSkin.window.border.Set(10, 10, 20, 10); 74 | newSkin.window.normal.textColor = Color.white; 75 | 76 | newSkin.button.padding.Set(4, 4, 3, 3); 77 | newSkin.button.normal.textColor = Color.white; 78 | 79 | newSkin.textField.normal.textColor = Color.white; 80 | 81 | newSkin.label.normal.textColor = Color.white; 82 | 83 | newSkin.toggle.stretchWidth = false; 84 | newSkin.label.stretchWidth = false; 85 | 86 | return newSkin; 87 | } 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/UI/guisharp-box.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManlyMarco/RuntimeUnityEditor/1cb4bc68ece99c0d0b7bdf9149831f8a59ca0dcb/RuntimeUnityEditor.Core/Utils/UI/guisharp-box.png -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/UI/guisharp-box.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManlyMarco/RuntimeUnityEditor/1cb4bc68ece99c0d0b7bdf9149831f8a59ca0dcb/RuntimeUnityEditor.Core/Utils/UI/guisharp-box.xcf -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/UI/guisharp-window.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManlyMarco/RuntimeUnityEditor/1cb4bc68ece99c0d0b7bdf9149831f8a59ca0dcb/RuntimeUnityEditor.Core/Utils/UI/guisharp-window.png -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Utils/UI/guisharp-window.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ManlyMarco/RuntimeUnityEditor/1cb4bc68ece99c0d0b7bdf9149831f8a59ca0dcb/RuntimeUnityEditor.Core/Utils/UI/guisharp-window.xcf -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/WindowManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using RuntimeUnityEditor.Core.REPL; 4 | using UnityEngine; 5 | 6 | namespace RuntimeUnityEditor.Core 7 | { 8 | /// 9 | /// API for organizing and managing windows. 10 | /// 11 | public class WindowManager 12 | { 13 | /// 14 | /// Default distance of windows from screen corners and other windows. 15 | /// 16 | private const int ScreenMargin = 10; 17 | 18 | /// 19 | /// Default width of windows shown on left and right sides. 20 | /// 21 | private const int SideWidth = 350; 22 | 23 | /// 24 | /// Get default size of a window for a given screen client rectangle and desired screen partition. 25 | /// 26 | public static Rect MakeDefaultWindowRect(Rect screenClientRect, ScreenPartition screenPartition) 27 | { 28 | switch (screenPartition) 29 | { 30 | case ScreenPartition.Left: 31 | return EnsureVisible(new Rect(screenClientRect.xMin, screenClientRect.yMin, SideWidth, screenClientRect.height)); 32 | case ScreenPartition.LeftUpper: 33 | return EnsureVisible(new Rect(screenClientRect.xMin, screenClientRect.yMin, SideWidth, screenClientRect.height / 2 - ScreenMargin)); 34 | case ScreenPartition.LeftLower: 35 | return EnsureVisible(new Rect(screenClientRect.xMin, screenClientRect.yMin + screenClientRect.height / 2, SideWidth, screenClientRect.height / 2)); 36 | 37 | case ScreenPartition.Center: 38 | { 39 | var centerWidth = (int)Mathf.Min(850, screenClientRect.width); 40 | var centerX = (int)(screenClientRect.xMin + screenClientRect.width / 2 - Mathf.RoundToInt((float)centerWidth / 2)); 41 | return EnsureVisible(new Rect(centerX, screenClientRect.yMin, centerWidth, screenClientRect.height)); 42 | } 43 | case ScreenPartition.CenterUpper: 44 | { 45 | var centerWidth = (int)Mathf.Min(850, screenClientRect.width); 46 | var centerX = (int)(screenClientRect.xMin + screenClientRect.width / 2 - Mathf.RoundToInt((float)centerWidth / 2)); 47 | var upperHeight = (int)(screenClientRect.height / 4) * 3; 48 | return EnsureVisible(new Rect(centerX, screenClientRect.yMin, centerWidth, upperHeight)); 49 | } 50 | case ScreenPartition.CenterLower: 51 | { 52 | var centerWidth = (int)Mathf.Min(850, screenClientRect.width); 53 | var centerX = (int)(screenClientRect.xMin + screenClientRect.width / 2 - Mathf.RoundToInt((float)centerWidth / 2)); 54 | var upperHeight = (int)(screenClientRect.height / 4) * 3; 55 | return EnsureVisible(new Rect(centerX, screenClientRect.yMin + upperHeight + ScreenMargin, centerWidth, screenClientRect.height - upperHeight - ScreenMargin)); 56 | } 57 | 58 | case ScreenPartition.Right: 59 | return EnsureVisible(new Rect(screenClientRect.xMax - SideWidth, screenClientRect.yMin, SideWidth, screenClientRect.height)); 60 | case ScreenPartition.RightUpper: 61 | return EnsureVisible(new Rect(screenClientRect.xMax - SideWidth, screenClientRect.yMin, SideWidth, screenClientRect.height / 2)); 62 | case ScreenPartition.RightLower: 63 | return EnsureVisible(new Rect(screenClientRect.xMax - SideWidth, screenClientRect.yMin + screenClientRect.height / 2, SideWidth, screenClientRect.height / 2)); 64 | 65 | case ScreenPartition.Full: 66 | return screenClientRect; 67 | 68 | case ScreenPartition.Default: 69 | if (ReplWindow.Initialized) 70 | goto case ScreenPartition.CenterUpper; 71 | else 72 | goto case ScreenPartition.Center; 73 | 74 | default: 75 | throw new ArgumentOutOfRangeException(nameof(screenPartition), screenPartition, null); 76 | } 77 | } 78 | 79 | private static Rect EnsureVisible(Rect rect) 80 | { 81 | var result = rect; 82 | var allWindows = RuntimeUnityEditorCore.Instance.InitializedFeatures.OfType(); 83 | var allWindowsRects = allWindows.Select(w => w.WindowRect).ToList(); 84 | // Check if any window near this position, move the rect until it's not near any other window 85 | while (allWindowsRects.Any(r => Mathf.Abs(r.x - result.x) < 7 && Mathf.Abs(r.y - result.y) < 7)) 86 | { 87 | result.x += 17; 88 | result.y += 17; 89 | } 90 | // Ensure the new rect is visible on screen 91 | return result.x < Screen.width - 50 && result.y < Screen.height - 50 ? result : rect; 92 | } 93 | 94 | /// 95 | /// Discard current window size and position, and set the default ones for the given window. 96 | /// 97 | public static void ResetWindowRect(IWindow window) 98 | { 99 | var screenClientRect = new Rect( 100 | x: ScreenMargin, 101 | y: ScreenMargin, 102 | width: Screen.width - ScreenMargin * 2, 103 | height: Screen.height - ScreenMargin * 2 - Taskbar.Instance.Height); 104 | 105 | window.WindowRect = MakeDefaultWindowRect(screenClientRect, window.DefaultScreenPosition); 106 | } 107 | 108 | /// 109 | /// Check if the window rect of a given window is visible on the screen and of appropriate size. 110 | /// 111 | public static bool IsWindowRectValid(IWindow window) 112 | { 113 | return window.WindowRect.width >= window.MinimumSize.x && 114 | window.WindowRect.height >= window.MinimumSize.y && 115 | window.WindowRect.x < Screen.width - ScreenMargin && 116 | window.WindowRect.y < Screen.height - ScreenMargin && 117 | window.WindowRect.x >= -window.WindowRect.width + ScreenMargin && 118 | window.WindowRect.y >= -window.WindowRect.height + ScreenMargin; 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Breakpoints/BreakpointHit.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.Linq; 4 | 5 | namespace RuntimeUnityEditor.Core.Breakpoints 6 | { 7 | /// 8 | /// Represents a hit on a breakpoint. 9 | /// 10 | public sealed class BreakpointHit 11 | { 12 | /// 13 | /// Create a new breakpoint hit. 14 | /// 15 | public BreakpointHit(BreakpointPatchInfo origin, object instance, object[] args, object result, StackTrace trace) 16 | { 17 | Origin = origin; 18 | Instance = instance; 19 | Args = args; 20 | Result = result; 21 | Trace = trace; 22 | TraceString = trace.ToString(); 23 | Time = DateTime.UtcNow; 24 | } 25 | 26 | /// 27 | /// The breakpoint that was hit. 28 | /// 29 | public readonly BreakpointPatchInfo Origin; 30 | /// 31 | /// The instance that was used to call the method. 32 | /// 33 | public readonly object Instance; 34 | /// 35 | /// The arguments that were passed to the method. 36 | /// 37 | public readonly object[] Args; 38 | /// 39 | /// The result of the method call. 40 | /// 41 | public readonly object Result; 42 | /// 43 | /// The stack trace at the time of the hit. 44 | /// 45 | public readonly StackTrace Trace; 46 | internal readonly string TraceString; 47 | /// 48 | /// The time at which the breakpoint was hit. 49 | /// 50 | public readonly DateTime Time; 51 | 52 | private string _toStr, _searchStr; 53 | /// 54 | /// Returns a string that can be used to search for this breakpoint hit. 55 | /// 56 | public string GetSearchableString() 57 | { 58 | if (_searchStr == null) 59 | _searchStr = $"{Origin.Target.DeclaringType?.FullName}.{Origin.Target.Name}\t{Result}\t{string.Join("\t", Args.Select(x => x?.ToString() ?? "").ToArray())}"; 60 | return _searchStr; 61 | } 62 | /// 63 | public override string ToString() 64 | { 65 | if (_toStr == null) 66 | _toStr = $"{Origin.Target.DeclaringType?.FullName ?? "???"}.{Origin.Target.Name} |Result> {Result?.ToString() ?? "NULL"} |Args> {string.Join(" | ", Args.Select(x => x?.ToString() ?? "NULL").ToArray())}"; 67 | return _toStr; 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Breakpoints/BreakpointHitException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RuntimeUnityEditor.Core.Breakpoints 4 | { 5 | internal sealed class BreakpointHitException : Exception 6 | { 7 | public BreakpointHitException(string message) : base(message) { } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Breakpoints/BreakpointPatchInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Linq; 3 | using System.Reflection; 4 | 5 | namespace RuntimeUnityEditor.Core.Breakpoints 6 | { 7 | /// 8 | /// Information about a breakpoint patch. 9 | /// 10 | public sealed class BreakpointPatchInfo 11 | { 12 | /// 13 | /// The method that was patched. 14 | /// 15 | public MethodBase Target { get; } 16 | /// 17 | /// The method that will be called when the breakpoint is hit. 18 | /// 19 | public MethodInfo Patch { get; } 20 | /// 21 | /// The instance filters that will be used to determine if the breakpoint should be hit. 22 | /// 23 | public List InstanceFilters { get; } = new List(); 24 | 25 | /// 26 | /// Create a new breakpoint patch info. 27 | /// 28 | public BreakpointPatchInfo(MethodBase target, MethodInfo patch, object instanceFilter) 29 | { 30 | Target = target; 31 | Patch = patch; 32 | if (instanceFilter != null) 33 | InstanceFilters.Add(instanceFilter); 34 | } 35 | 36 | private string _toStr, _searchStr; 37 | 38 | internal string GetSearchableString() 39 | { 40 | if (_searchStr == null) 41 | _searchStr = $"{Target.DeclaringType?.FullName}.{Target.Name}\t{string.Join("\t", InstanceFilters.Select(x => x?.ToString()).ToArray())}"; 42 | return _searchStr; 43 | } 44 | /// 45 | public override string ToString() 46 | { 47 | if (_toStr == null) 48 | _toStr = $"{Target.DeclaringType?.FullName ?? "???"}.{Target.Name} |Instances> {string.Join(" | ", InstanceFilters.Select(x => x?.ToString() ?? "NULL").ToArray())}"; 49 | return _toStr; 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Breakpoints/Breakpoints.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Reflection; 5 | using HarmonyLib; 6 | 7 | namespace RuntimeUnityEditor.Core.Breakpoints 8 | { 9 | /// 10 | /// Class for managing breakpoints in methods. 11 | /// 12 | public static class Breakpoints 13 | { 14 | private static readonly Harmony _harmony = new Harmony("RuntimeUnityEditor.Core.Breakpoints"); 15 | private static readonly HarmonyMethod _handlerMethodRet = new HarmonyMethod(typeof(Hooks), nameof(Hooks.BreakpointHandlerReturn)); 16 | private static readonly HarmonyMethod _handlerMethodNoRet = new HarmonyMethod(typeof(Hooks), nameof(Hooks.BreakpointHandlerNoReturn)); 17 | private static readonly Dictionary _appliedPatches = new Dictionary(); 18 | /// 19 | /// A collection of all applied patches. 20 | /// 21 | public static ICollection AppliedPatches => _appliedPatches.Values; 22 | 23 | /// 24 | /// Whether all breakpoints are enabled or not. 25 | /// 26 | public static bool Enabled { get; set; } = true; 27 | 28 | /// 29 | /// What to do when a breakpoint is hit. 30 | /// 31 | public static DebuggerBreakType DebuggerBreaking { get; set; } 32 | 33 | /// 34 | /// Event that is called when a breakpoint is hit. 35 | /// 36 | public static event Action OnBreakpointHit; 37 | 38 | /// 39 | /// Attaches a breakpoint to a method. 40 | /// 41 | /// Method to attach to 42 | /// Only trigger when method is called on this instance. If null then break on all calls. 43 | /// True if patch was applied successfully 44 | public static bool AttachBreakpoint(MethodBase target, object instance) 45 | { 46 | if (_appliedPatches.TryGetValue(target, out var pi)) 47 | { 48 | if (instance != null) 49 | pi.InstanceFilters.Add(instance); 50 | else 51 | pi.InstanceFilters.Clear(); 52 | return true; 53 | } 54 | 55 | var hasReturn = target is MethodInfo mi && mi.ReturnType != typeof(void); 56 | var patch = _harmony.Patch(target, postfix: hasReturn ? _handlerMethodRet : _handlerMethodNoRet); 57 | if (patch != null) 58 | { 59 | _appliedPatches[target] = new BreakpointPatchInfo(target, patch, instance); 60 | return true; 61 | } 62 | 63 | return false; 64 | } 65 | 66 | /// 67 | /// Detaches a breakpoint from a method. 68 | /// 69 | /// Method to detach from 70 | /// Only remove this instance filter. If null, completely remove this breakpoint. 71 | /// True if the breakpoint was completely removed, false otherwise. 72 | public static bool DetachBreakpoint(MethodBase target, object instance) 73 | { 74 | if (_appliedPatches.TryGetValue(target, out var pi)) 75 | { 76 | if (instance == null) 77 | pi.InstanceFilters.Clear(); 78 | else 79 | pi.InstanceFilters.Remove(instance); 80 | 81 | if (pi.InstanceFilters.Count == 0) 82 | { 83 | _harmony.Unpatch(target, pi.Patch); 84 | _appliedPatches.Remove(target); 85 | return true; 86 | } 87 | } 88 | 89 | return false; 90 | } 91 | 92 | /// 93 | /// Checks if a breakpoint is attached to a method. 94 | /// 95 | public static bool IsAttached(MethodBase target, object instance) 96 | { 97 | if (_appliedPatches.TryGetValue(target, out var pi)) 98 | { 99 | return instance == null && pi.InstanceFilters.Count == 0 || pi.InstanceFilters.Contains(instance); 100 | } 101 | 102 | return false; 103 | } 104 | 105 | /// 106 | /// Detaches all breakpoints. 107 | /// 108 | public static void DetachAll() 109 | { 110 | _harmony.UnpatchSelf(); 111 | _appliedPatches.Clear(); 112 | } 113 | 114 | private static void AddHit(object __instance, MethodBase __originalMethod, object[] __args, object __result) 115 | { 116 | if (!Enabled) return; 117 | 118 | if (!_appliedPatches.TryGetValue(__originalMethod, out var pi)) return; 119 | 120 | if (pi.InstanceFilters.Count > 0 && !pi.InstanceFilters.Contains(__instance)) return; 121 | 122 | if (DebuggerBreaking == DebuggerBreakType.ThrowCatch) 123 | { 124 | try { throw new BreakpointHitException(pi.Target.Name); } 125 | catch (BreakpointHitException) { } 126 | } 127 | else if (DebuggerBreaking == DebuggerBreakType.DebuggerBreak) 128 | { 129 | Debugger.Break(); 130 | } 131 | 132 | OnBreakpointHit?.Invoke(new BreakpointHit(pi, __instance, __args, __result, new StackTrace(2, true))); 133 | } 134 | 135 | private static class Hooks 136 | { 137 | public static void BreakpointHandlerReturn(object __instance, MethodBase __originalMethod, object[] __args, object __result) 138 | { 139 | AddHit(__instance, __originalMethod, __args, __result); 140 | } 141 | 142 | public static void BreakpointHandlerNoReturn(object __instance, MethodBase __originalMethod, object[] __args) 143 | { 144 | AddHit(__instance, __originalMethod, __args, null); 145 | } 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Breakpoints/DebuggerBreakType.cs: -------------------------------------------------------------------------------- 1 | namespace RuntimeUnityEditor.Core.Breakpoints 2 | { 3 | /// 4 | /// What debugger-related action should happen when a breakpoint is hit. 5 | /// 6 | public enum DebuggerBreakType 7 | { 8 | /// 9 | /// Do nothing. 10 | /// 11 | None = 0, 12 | /// 13 | /// Call Debugger.Break. 14 | /// 15 | DebuggerBreak, 16 | /// 17 | /// Throw . 18 | /// 19 | ThrowCatch 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/ChangeHistory/ChangeAction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RuntimeUnityEditor.Core.ChangeHistory 4 | { 5 | internal class ChangeAction : IChange 6 | { 7 | public ChangeAction(string actionNameFormat, TObj target, Action undoAction) 8 | { 9 | if (actionNameFormat == null) throw new ArgumentNullException(nameof(actionNameFormat)); 10 | Target = target; 11 | _undoAction = undoAction; 12 | 13 | // Check if we need to format the target object. 14 | // If user included format info like {0:00} then pass the Target object through as it is. 15 | var targetFormatted = actionNameFormat.Contains("{0}") ? Change.GetTargetDisplayString(Target) : (object)Target; 16 | _displayString = string.Format(actionNameFormat, targetFormatted); 17 | } 18 | 19 | public ChangeAction(string actionName, Action undoAction = null) 20 | { 21 | _displayString = actionName ?? throw new ArgumentNullException(nameof(actionName)); 22 | if (undoAction != null) _undoAction = _ => undoAction(); 23 | } 24 | 25 | public TObj Target { get; } 26 | object IChange.Target => Target; 27 | 28 | private readonly Action _undoAction; 29 | public bool CanUndo => _undoAction != null; //todo make this single-shot? 30 | public void Undo() 31 | { 32 | if (!CanUndo) throw new InvalidOperationException("Can't undo this change"); 33 | 34 | _undoAction(Target); 35 | } 36 | 37 | private readonly string _displayString; 38 | public string GetDisplayString() => _displayString; 39 | public DateTime ChangeTime { get; } = DateTime.Now; 40 | } 41 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/ChangeHistory/ChangeAssignment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RuntimeUnityEditor.Core.ChangeHistory 4 | { 5 | internal class ChangeAssignment : IChange 6 | { 7 | public ChangeAssignment(TObj target, TVal newValue, TVal originalValue, Action undoAction, string actionNameFormat) 8 | { 9 | if (actionNameFormat == null) throw new ArgumentNullException(nameof(actionNameFormat)); 10 | 11 | Target = target; 12 | NewValue = newValue; 13 | OriginalValue = originalValue; 14 | _undoAction = undoAction; 15 | ActionNameFormat = actionNameFormat; 16 | } 17 | 18 | public TObj Target { get; } 19 | object IChange.Target => Target; 20 | 21 | public TVal NewValue { get; private set; } 22 | public TVal OriginalValue { get; } 23 | 24 | public bool CanUndo => _undoAction != null; 25 | private readonly Action _undoAction; 26 | public void Undo() 27 | { 28 | if (!CanUndo) throw new InvalidOperationException("Can't undo this change"); 29 | 30 | _undoAction(Target, OriginalValue); 31 | } 32 | 33 | /// 34 | /// String format used to get the name of this change. 35 | /// Has 2 available parameters, the target object {0} and the new value {1}. 36 | /// 37 | internal readonly string ActionNameFormat; 38 | private string _displayStringCached; 39 | public string GetDisplayString() 40 | { 41 | if (_displayStringCached != null) 42 | return _displayStringCached; 43 | 44 | // Check if we need to format the target object. 45 | // If user included format info like {0:00} then pass the Target object through as it is. 46 | var target = ActionNameFormat.Contains("{0}") ? Change.GetTargetDisplayString(Target) : (object)Target; 47 | 48 | return _displayStringCached = string.Format(ActionNameFormat, target, NewValue); 49 | } 50 | 51 | public DateTime ChangeTime { get; private set; } = DateTime.Now; 52 | 53 | public void UpdateNewValue(TVal newValue) 54 | { 55 | NewValue = newValue; 56 | _displayStringCached = null; 57 | ChangeTime = DateTime.Now; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/ChangeHistory/ChangeHistoryWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | using RuntimeUnityEditor.Core.Inspector.Entries; 5 | using RuntimeUnityEditor.Core.Utils; 6 | using RuntimeUnityEditor.Core.Utils.Abstractions; 7 | using UnityEngine; 8 | 9 | namespace RuntimeUnityEditor.Core.ChangeHistory 10 | { 11 | /// 12 | /// UI window that displays a list of changes made to the game by RUE (or more specifically by everything that used the API). 13 | /// 14 | public class ChangeHistoryWindow : Window 15 | { 16 | private static readonly GUIContent _inspectContent = new GUIContent("Insp.", null, "Inspect target object (the object that contains the changed member, not the changed member itself)"); 17 | private static readonly GUIContent _undoContent = new GUIContent("Undo", null, "Attempt to undo this action. The more changes were made to the affected object since this change, the less reliable this will be."); 18 | 19 | private Vector2 _scrollPos; 20 | private InitSettings.Setting _showTimestamps; 21 | 22 | /// 23 | protected override void Initialize(InitSettings initSettings) 24 | { 25 | DisplayName = "History"; 26 | Title = "Change History"; 27 | DefaultScreenPosition = ScreenPartition.LeftLower; 28 | 29 | _showTimestamps = initSettings.RegisterSetting("Change History", "Show timestamps in Change History window", true, string.Empty); 30 | } 31 | 32 | /// 33 | protected override void DrawContents() 34 | { 35 | GUILayout.BeginHorizontal(GUI.skin.box); 36 | { 37 | if (GUILayout.Button("Clear")) 38 | Change.Changes.Clear(); 39 | 40 | GUILayout.Space(5); 41 | 42 | if (GUILayout.Button("Copy all to clipboard")) 43 | { 44 | try 45 | { 46 | UnityFeatureHelper.systemCopyBuffer = string.Join("\n", Change.Changes.Select(c => c.GetDisplayString()).ToArray()); 47 | RuntimeUnityEditorCore.Logger.Log(LogLevel.Message, $"Copied {Change.Changes.Count} changes to clipboard"); 48 | } 49 | catch (Exception e) 50 | { 51 | RuntimeUnityEditorCore.Logger.Log(LogLevel.Message | LogLevel.Error, "Failed to copy to clipboard: " + e.Message); 52 | } 53 | } 54 | 55 | if (GUILayout.Button("...as pseudo-code")) 56 | { 57 | try 58 | { 59 | UnityFeatureHelper.systemCopyBuffer = string.Join("\n", Change.Changes.Select(ConvertChangeToPseudoCodeString).ToArray()); 60 | RuntimeUnityEditorCore.Logger.Log(LogLevel.Message, $"Copied {Change.Changes.Count} changes to clipboard (converted to pseudo-code)"); 61 | } 62 | catch (Exception e) 63 | { 64 | RuntimeUnityEditorCore.Logger.Log(LogLevel.Message | LogLevel.Error, "Failed to copy to clipboard: " + e.Message); 65 | } 66 | } 67 | 68 | GUILayout.FlexibleSpace(); 69 | 70 | GUI.changed = false; 71 | _showTimestamps.Value = GUILayout.Toggle(_showTimestamps.Value, "Show timestamps"); 72 | } 73 | GUILayout.EndHorizontal(); 74 | 75 | _scrollPos = GUILayout.BeginScrollView(_scrollPos, false, true); 76 | { 77 | foreach (var change in Change.Changes) 78 | { 79 | GUILayout.BeginHorizontal(GUI.skin.box); 80 | { 81 | if (_showTimestamps.Value) 82 | { 83 | GUI.color = new Color(0.67f, 0.67f, 0.67f); 84 | GUILayout.Label(change.ChangeTime.ToString("HH:mm:ss"), GUILayout.MinWidth(50)); 85 | GUI.color = Color.white; 86 | } 87 | 88 | GUILayout.TextField(change.GetDisplayString(), GUI.skin.label, IMGUIUtils.LayoutOptionsExpandWidthTrue); 89 | 90 | if (change.CanUndo && GUILayout.Button(_undoContent, IMGUIUtils.LayoutOptionsExpandWidthFalse)) 91 | { 92 | try 93 | { 94 | change.Undo(); 95 | } 96 | catch (Exception e) 97 | { 98 | RuntimeUnityEditorCore.Logger.Log(LogLevel.Warning | LogLevel.Message, "Failed to Undo: " + e.Message); 99 | } 100 | } 101 | 102 | if (!change.Target.IsNullOrDestroyed() && GUILayout.Button(_inspectContent, IMGUIUtils.LayoutOptionsExpandWidthFalse)) 103 | Inspector.Inspector.Instance.Push(new InstanceStackEntry(change.Target, change.GetDisplayString()), true); 104 | } 105 | GUILayout.EndHorizontal(); 106 | } 107 | } 108 | GUILayout.EndScrollView(); 109 | } 110 | 111 | private static string ConvertChangeToPseudoCodeString(IChange change) 112 | { 113 | var displayString = change.GetDisplayString(); 114 | var cleaned = Regex.Replace(displayString, @"^\([\w/ ]*?\)::", ""); 115 | cleaned = Regex.Replace(cleaned, @"\(([\w/ ]*?)\)::GameObject", "GameObject.Find(\"$1\")"); 116 | return cleaned.Length == 0 ? displayString : cleaned; 117 | } 118 | } 119 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/ChangeHistory/IChange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RuntimeUnityEditor.Core.ChangeHistory 4 | { 5 | /// 6 | /// Metadata representing a change that can optionally be undone. 7 | /// 8 | public interface IChange 9 | { 10 | /// 11 | /// Object whose members were affected by this change. 12 | /// 13 | object Target { get; } 14 | /// 15 | /// Whether this change can be undone. 16 | /// 17 | bool CanUndo { get; } 18 | /// 19 | /// Undo this change. Throws if is false. 20 | /// 21 | void Undo(); 22 | /// 23 | /// How to display this change in the UI. 24 | /// 25 | string GetDisplayString(); 26 | /// 27 | /// When this change was made. 28 | /// 29 | DateTime ChangeTime { get; } 30 | } 31 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/CacheEntryBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using RuntimeUnityEditor.Core.Utils; 4 | using UnityEngine; 5 | 6 | namespace RuntimeUnityEditor.Core.Inspector.Entries 7 | { 8 | /// 9 | public abstract class CacheEntryBase : ICacheEntry 10 | { 11 | /// 12 | /// Enable caching of the value returned by . This will speed up the inspector when inspecting large objects. 13 | /// TODO: Disabled because of relatively low performance impact. Maybe add as a gui option 14 | /// 15 | public static bool CachingEnabled { get; set; } = false; 16 | 17 | /// 18 | /// Constructor for the cache entry. 19 | /// 20 | protected CacheEntryBase(string name, string description, Type owner = null) 21 | { 22 | Owner = owner; 23 | _name = name; 24 | _nameContent = new GUIContent(_name, null, description + "\n\nLeft click to inspect in current tab\nMiddle click to inspect in a new tab\nRight click to open a menu with more options"); 25 | } 26 | 27 | /// 28 | /// Name of the member for display in the UI. 29 | /// 30 | public GUIContent GetNameContent() => _nameContent; 31 | 32 | /// 33 | /// Get object that is entered when variable name is clicked in inspector 34 | /// 35 | public virtual object EnterValue() 36 | { 37 | if (!CachingEnabled) return GetValue(); 38 | 39 | return _valueCache = (GetValueToCache() ?? GetValue()); 40 | } 41 | 42 | /// 43 | /// Get the member's value. This method is called when caching is disabled or when the cache is empty. 44 | /// 45 | public abstract object GetValueToCache(); 46 | private object _valueCache; 47 | /// 48 | /// Get the member's value, either from the cache or by calling . 49 | /// 50 | public virtual object GetValue() 51 | { 52 | if (!CachingEnabled) return GetValueToCache(); 53 | 54 | return _valueCache ?? (_valueCache = GetValueToCache()); 55 | } 56 | 57 | /// 58 | /// Set the member's value. This method is called when the value is changed in the inspector. 59 | /// 60 | public void SetValue(object newValue) 61 | { 62 | if (OnSetValue(newValue)) 63 | _valueCache = newValue; 64 | } 65 | 66 | /// 67 | /// Set the member's value. This method is called when the value is changed in the inspector. 68 | /// 69 | protected abstract bool OnSetValue(object newValue); 70 | 71 | /// 72 | /// Return/field type of this member. 73 | /// 74 | public abstract Type Type(); 75 | /// 76 | /// Member's reflection info. 77 | /// 78 | public abstract MemberInfo MemberInfo { get; } 79 | /// 80 | /// True if the value of this member can be set. This is used to determine if the member is editable in the inspector. 81 | /// 82 | public abstract bool CanSetValue(); 83 | 84 | private readonly string _name; 85 | private string _typeName; 86 | /// 87 | /// Type that contains this member. 88 | /// 89 | public Type Owner { get; } 90 | 91 | /// 92 | /// Name of the member. 93 | /// 94 | public string Name() => _name; 95 | 96 | /// 97 | /// Name of the member's field/return type for use in UI. 98 | /// 99 | public string TypeName() 100 | { 101 | if (_typeName == null) 102 | { 103 | var type = Type(); 104 | if (type != null) 105 | _typeName = type.GetSourceCodeRepresentation(); 106 | else 107 | _typeName = "INVALID"; 108 | } 109 | return _typeName; 110 | } 111 | 112 | private bool? _canEnter; 113 | private readonly GUIContent _nameContent; 114 | 115 | /// 116 | public virtual bool CanEnterValue() 117 | { 118 | if (_canEnter == null) 119 | { 120 | var type = Type(); 121 | _canEnter = type != null && !type.IsPrimitive; 122 | } 123 | return _canEnter.Value; 124 | } 125 | 126 | /// 127 | public int ItemHeight { get; set; } = Inspector.InspectorRecordInitialHeight; 128 | } 129 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/CallbackCacheEntey.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace RuntimeUnityEditor.Core.Inspector.Entries 5 | { 6 | /// 7 | public class CallbackCacheEntry : CacheEntryBase 8 | { 9 | private readonly string _message; 10 | private readonly Action _callback; 11 | 12 | /// 13 | public CallbackCacheEntry(string name, string message, Action callback) : base(name, "RUE Callback / Feature") 14 | { 15 | _message = message; 16 | _callback = callback ?? throw new ArgumentNullException(nameof(callback)); 17 | } 18 | 19 | /// 20 | public override object GetValueToCache() 21 | { 22 | return _message; 23 | } 24 | 25 | /// 26 | public override bool CanEnterValue() 27 | { 28 | return true; 29 | } 30 | 31 | /// 32 | public override object EnterValue() 33 | { 34 | _callback(); 35 | return null; 36 | } 37 | 38 | /// 39 | protected override bool OnSetValue(object newValue) 40 | { 41 | return false; 42 | } 43 | 44 | /// 45 | public override Type Type() 46 | { 47 | return typeof(void); 48 | } 49 | 50 | /// 51 | public override MemberInfo MemberInfo => null; 52 | 53 | /// 54 | public override bool CanSetValue() 55 | { 56 | return false; 57 | } 58 | } 59 | 60 | /// 61 | public class CallbackCacheEntry : CacheEntryBase 62 | { 63 | private readonly string _message; 64 | private readonly Func _callback; 65 | 66 | /// 67 | public CallbackCacheEntry(string name, string message, Func callback) : base(name, "RUE Callback / Feature") 68 | { 69 | _message = message; 70 | _callback = callback ?? throw new ArgumentNullException(nameof(callback)); 71 | } 72 | 73 | /// 74 | public override object GetValueToCache() 75 | { 76 | return _message; 77 | } 78 | 79 | /// 80 | public override bool CanEnterValue() 81 | { 82 | return true; 83 | } 84 | 85 | /// 86 | public override object EnterValue() 87 | { 88 | return _callback(); 89 | } 90 | 91 | /// 92 | protected override bool OnSetValue(object newValue) 93 | { 94 | return false; 95 | } 96 | 97 | /// 98 | public override Type Type() 99 | { 100 | return typeof(T); 101 | } 102 | 103 | /// 104 | public override MemberInfo MemberInfo { get; } 105 | 106 | /// 107 | public override bool CanSetValue() 108 | { 109 | return false; 110 | } 111 | } 112 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/EventCacheEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using RuntimeUnityEditor.Core.Utils; 4 | 5 | namespace RuntimeUnityEditor.Core.Inspector.Entries 6 | { 7 | /// 8 | public class EventCacheEntry : CacheEntryBase 9 | { 10 | /// 11 | /// Instance of the object that owns the event. 12 | /// 13 | public object Instance { get; } 14 | 15 | /// 16 | /// EventInfo for the event. 17 | /// 18 | public EventInfo EventInfo { get; } 19 | 20 | /// 21 | public EventCacheEntry(object ins, EventInfo e, Type owner) : base(FieldCacheEntry.GetMemberName(ins, e), e.GetFancyDescription(), owner) 22 | { 23 | if (owner == null) throw new ArgumentNullException(nameof(owner)); 24 | Instance = ins; 25 | EventInfo = e ?? throw new ArgumentNullException(nameof(e)); 26 | BackingField = owner.GetField(e.Name, BindingFlags.NonPublic | (ins == null ? BindingFlags.Static : BindingFlags.Instance)); 27 | } 28 | 29 | /// 30 | /// Backing field for the event. This is used to inspect the event. 31 | /// 32 | public FieldInfo BackingField { get; } 33 | 34 | /// 35 | public override bool CanEnterValue() => BackingField != null; 36 | /// 37 | public override object GetValueToCache() => BackingField?.GetValue(Instance); 38 | /// 39 | protected override bool OnSetValue(object newValue) => throw new InvalidOperationException(); 40 | /// 41 | public override Type Type() => EventInfo.EventHandlerType; 42 | /// 43 | public override MemberInfo MemberInfo => EventInfo; 44 | /// 45 | public override bool CanSetValue() => false; 46 | 47 | /// 48 | /// Checks if the event is declared in the owner type. 49 | /// 50 | public bool IsDeclared => Owner == EventInfo.DeclaringType; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/FieldCacheEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using RuntimeUnityEditor.Core.ChangeHistory; 4 | using RuntimeUnityEditor.Core.Utils; 5 | 6 | namespace RuntimeUnityEditor.Core.Inspector.Entries 7 | { 8 | /// 9 | public class FieldCacheEntry : CacheEntryBase 10 | { 11 | /// 12 | public FieldCacheEntry(object ins, FieldInfo f, Type owner) : this(ins, f, owner, null) { } 13 | /// 14 | public FieldCacheEntry(object ins, FieldInfo f, Type owner, ICacheEntry parent) : base(GetMemberName(ins, f), f.GetFancyDescription(), owner) 15 | { 16 | _instance = ins; 17 | FieldInfo = f ?? throw new ArgumentNullException(nameof(f)); 18 | _parent = parent; 19 | } 20 | 21 | internal static string GetMemberName(object ins, MemberInfo f) 22 | { 23 | if (ins != null) 24 | return f?.Name; 25 | return "S/" + f?.Name; 26 | } 27 | 28 | /// 29 | /// FieldInfo for the field. 30 | /// 31 | public FieldInfo FieldInfo { get; } 32 | /// 33 | /// If the field is declared in the owner type. 34 | /// 35 | public bool IsDeclared => Owner == FieldInfo.DeclaringType; 36 | private readonly object _instance; 37 | private readonly ICacheEntry _parent; 38 | 39 | /// 40 | public override object GetValueToCache() => FieldInfo.GetValue(_instance); 41 | 42 | /// 43 | protected override bool OnSetValue(object newValue) 44 | { 45 | if (!FieldInfo.IsInitOnly) 46 | { 47 | Change.MemberAssignment(_instance, newValue, FieldInfo); 48 | // Needed for structs to propagate changes back to the original field/prop 49 | if (_parent != null && _parent.CanSetValue()) _parent.SetValue(_instance); 50 | return true; 51 | } 52 | return false; 53 | } 54 | 55 | /// 56 | public override Type Type() 57 | { 58 | return FieldInfo.FieldType; 59 | } 60 | 61 | /// 62 | public override MemberInfo MemberInfo => FieldInfo; 63 | 64 | /// 65 | public override bool CanSetValue() 66 | { 67 | return (FieldInfo.Attributes & FieldAttributes.Literal) == 0 && !FieldInfo.IsInitOnly && (_parent == null || _parent.CanSetValue()); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/ICacheEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using UnityEngine; 4 | 5 | namespace RuntimeUnityEditor.Core.Inspector.Entries 6 | { 7 | /// 8 | /// Representation of a type's member or some other entity that is shown inside the Inspector's member list. 9 | /// 10 | public interface ICacheEntry 11 | { 12 | /// 13 | /// Name of the member. 14 | /// 15 | string Name(); 16 | /// 17 | /// Name of the member's field/return type. 18 | /// 19 | string TypeName(); 20 | /// 21 | /// Name content shown in the UI. 22 | /// 23 | GUIContent GetNameContent(); 24 | /// 25 | /// Type that owns this member. 26 | /// 27 | Type Owner { get; } 28 | /// 29 | /// Get object that is entered when variable name is clicked in inspector 30 | /// 31 | object EnterValue(); 32 | /// 33 | /// Get the member's value. 34 | /// 35 | object GetValue(); 36 | /// 37 | /// Set the member's value. 38 | /// 39 | void SetValue(object newValue); 40 | /// 41 | /// Member's field/return type. 42 | /// 43 | Type Type(); 44 | /// 45 | /// Member's reflection info. 46 | /// 47 | MemberInfo MemberInfo { get; } 48 | /// 49 | /// Value of this member can be set. 50 | /// 51 | bool CanSetValue(); 52 | /// 53 | /// Inspector can further inspect the value returned by this member. 54 | /// 55 | bool CanEnterValue(); 56 | /// 57 | /// Height of the item's row in the inspector's scroll list. 58 | /// 59 | int ItemHeight { get; set; } 60 | } 61 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/ListCacheEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Reflection; 4 | using HarmonyLib; 5 | using RuntimeUnityEditor.Core.ChangeHistory; 6 | 7 | namespace RuntimeUnityEditor.Core.Inspector.Entries 8 | { 9 | /// 10 | /// Represents a cache entry for an item inside any IList. 11 | /// 12 | public class ListCacheEntry : CacheEntryBase 13 | { 14 | private Type _type; 15 | private readonly IList _list; 16 | private readonly int _index; 17 | 18 | /// 19 | public ListCacheEntry(IList container, int index) : base(ReadonlyListCacheEntry.GetListItemName(index), $"Item contained inside of a list.\n\nIndex: {index}\n\nList type: {container.GetType().FullDescription()}", null) 20 | { 21 | _index = index; 22 | _list = container; 23 | } 24 | 25 | /// 26 | public override object GetValueToCache() 27 | { 28 | return _list.Count > _index ? _list[_index] : "ERROR: The list was changed while browsing!"; 29 | } 30 | 31 | /// 32 | protected override bool OnSetValue(object newValue) 33 | { 34 | if (CanSetValue()) 35 | { 36 | var oldValue = _list[_index]; 37 | Change.WithUndo($"{{0}}[{_index}] = {{1}}", _list, newValue, (list, o) => list[_index] = o, oldValue: oldValue); 38 | _type = null; 39 | return true; 40 | } 41 | 42 | return false; 43 | } 44 | 45 | /// 46 | public override Type Type() 47 | { 48 | return _type ?? (_type = GetValue()?.GetType()); 49 | } 50 | 51 | /// 52 | public override MemberInfo MemberInfo => null; 53 | 54 | /// 55 | public override bool CanSetValue() 56 | { 57 | return !_list.IsReadOnly; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/MethodCacheEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using HarmonyLib; 4 | using RuntimeUnityEditor.Core.Utils; 5 | using UnityEngine; 6 | 7 | namespace RuntimeUnityEditor.Core.Inspector.Entries 8 | { 9 | /// 10 | /// Represents a method entry in the inspector. 11 | /// 12 | public class MethodCacheEntry : ICacheEntry 13 | { 14 | /// 15 | /// Creates a new instance of MethodCacheEntry. 16 | /// 17 | public MethodCacheEntry(object instance, MethodInfo methodInfo, Type owner) 18 | { 19 | Instance = instance; 20 | MethodInfo = methodInfo ?? throw new ArgumentNullException(nameof(methodInfo)); 21 | Owner = owner ?? throw new ArgumentNullException(nameof(owner)); 22 | 23 | _name = FieldCacheEntry.GetMemberName(instance, methodInfo); 24 | _returnTypeName = MethodInfo.ReturnType.GetSourceCodeRepresentation(); 25 | 26 | ParameterString = GetParameterPreviewString(methodInfo); 27 | 28 | _content = new GUIContent(_name,null, methodInfo.GetFancyDescription()); 29 | } 30 | 31 | internal static string GetParameterPreviewString(MethodBase methodInfo) 32 | { 33 | var parameterString = string.Empty; 34 | var strGenerics = methodInfo.GetGenericArgumentsSafe().Join(p => p.FullDescription(), ", "); 35 | if (strGenerics.Length > 0) parameterString += "<" + strGenerics + ">"; 36 | var strParams = methodInfo.GetParameters().Join(p => p.ParameterType.FullDescription() + " " + p.Name, ", "); 37 | parameterString += "(" + strParams + ")"; 38 | return parameterString; 39 | } 40 | 41 | /// 42 | /// MethodInfo for the method. 43 | /// 44 | public MethodInfo MethodInfo { get; } 45 | 46 | /// 47 | /// Checks if the method is declared in the owner type. 48 | /// 49 | public bool IsDeclared => Owner == MethodInfo.DeclaringType; 50 | 51 | /// 52 | /// The type of the owner of the method. 53 | /// 54 | public Type Owner { get; } 55 | 56 | /// 57 | /// The instance of the object that owns the method. 58 | /// 59 | public object Instance { get; } 60 | 61 | /// 62 | /// String representation of the method parameters for use in UI. 63 | /// 64 | public string ParameterString { get; } 65 | 66 | private readonly string _name; 67 | private readonly string _returnTypeName; 68 | private readonly GUIContent _content; 69 | 70 | /// 71 | /// Name of the method. 72 | /// 73 | public string Name() => _name; 74 | 75 | /// 76 | /// Name of the method's return type for use in UI. 77 | /// 78 | public string TypeName() => _returnTypeName; 79 | 80 | /// 81 | public GUIContent GetNameContent() => _content; 82 | 83 | /// 84 | /// Not supported for methods. 85 | /// 86 | public object EnterValue() => throw new InvalidOperationException(); 87 | 88 | /// 89 | /// Not supported for methods. 90 | /// 91 | public object GetValue() => null; 92 | 93 | /// 94 | /// Not supported for methods. 95 | /// 96 | public void SetValue(object newValue) => throw new InvalidOperationException(); 97 | 98 | /// 99 | public Type Type() => MethodInfo.ReturnType; 100 | 101 | /// 102 | /// Method's reflection info. 103 | /// 104 | public MemberInfo MemberInfo => MethodInfo; 105 | 106 | /// 107 | /// Not supported for methods. 108 | /// 109 | public bool CanSetValue() => false; 110 | 111 | /// 112 | /// Not supported for methods. 113 | /// 114 | public bool CanEnterValue() => false; 115 | 116 | /// 117 | public int ItemHeight { get; set; } = Inspector.InspectorRecordInitialHeight; 118 | } 119 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/PropertyCacheEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using RuntimeUnityEditor.Core.ChangeHistory; 4 | using RuntimeUnityEditor.Core.Utils; 5 | 6 | namespace RuntimeUnityEditor.Core.Inspector.Entries 7 | { 8 | /// 9 | /// Represents a cache entry for a property in the inspector. 10 | /// 11 | public class PropertyCacheEntry : CacheEntryBase 12 | { 13 | /// 14 | public PropertyCacheEntry(object ins, PropertyInfo p, Type owner) : this(ins, p, owner, null) { } 15 | /// 16 | public PropertyCacheEntry(object ins, PropertyInfo p, Type owner, ICacheEntry parent) : base(FieldCacheEntry.GetMemberName(ins, p), p.GetFancyDescription(), owner) 17 | { 18 | _instance = ins; 19 | PropertyInfo = p ?? throw new ArgumentNullException(nameof(p)); 20 | _parent = parent; 21 | } 22 | 23 | /// 24 | /// PropertyInfo for the property. 25 | /// 26 | public PropertyInfo PropertyInfo { get; } 27 | /// 28 | /// Checks if the property is declared in the owner type. 29 | /// 30 | public bool IsDeclared => Owner == PropertyInfo.DeclaringType; 31 | private readonly object _instance; 32 | private readonly ICacheEntry _parent; 33 | 34 | /// 35 | public override bool CanEnterValue() => PropertyInfo.CanRead && base.CanEnterValue(); 36 | 37 | /// 38 | public override object GetValueToCache() 39 | { 40 | if (!PropertyInfo.CanRead) 41 | return "WRITE ONLY"; 42 | 43 | try 44 | { 45 | return PropertyInfo.GetValue(_instance, null); 46 | } 47 | catch (TargetInvocationException ex) 48 | { 49 | return ex.InnerException ?? ex; 50 | } 51 | catch (Exception ex) 52 | { 53 | return ex; 54 | } 55 | } 56 | 57 | /// 58 | protected override bool OnSetValue(object newValue) 59 | { 60 | if (PropertyInfo.CanWrite) 61 | { 62 | Change.MemberAssignment(_instance, newValue, PropertyInfo); 63 | // Needed for structs to propagate changes back to the original field/prop 64 | if (_parent != null && _parent.CanSetValue()) _parent.SetValue(_instance); 65 | return true; 66 | } 67 | return false; 68 | } 69 | 70 | /// 71 | public override Type Type() 72 | { 73 | return PropertyInfo.PropertyType; 74 | } 75 | 76 | /// 77 | public override MemberInfo MemberInfo => PropertyInfo; 78 | 79 | /// 80 | public override bool CanSetValue() 81 | { 82 | return PropertyInfo.CanWrite && (_parent == null || _parent.CanSetValue()); 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/ReadonlyCacheEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | 4 | namespace RuntimeUnityEditor.Core.Inspector.Entries 5 | { 6 | /// 7 | /// Represents a read-only cache entry in the inspector. 8 | /// 9 | public class ReadonlyCacheEntry : CacheEntryBase 10 | { 11 | /// 12 | /// The object that this entry represents. 13 | /// 14 | public readonly object Object; 15 | private readonly Type _type; 16 | private string _tostringCache; 17 | 18 | /// 19 | public ReadonlyCacheEntry(string name, object obj) : base(name, "Read-only item (RUE-only, it doesn't actually exist).") 20 | { 21 | Object = obj; 22 | _type = obj.GetType(); 23 | } 24 | 25 | /// 26 | public override object GetValueToCache() 27 | { 28 | return Object; 29 | } 30 | 31 | /// 32 | /// Always false. 33 | /// 34 | protected override bool OnSetValue(object newValue) 35 | { 36 | return false; 37 | } 38 | 39 | /// 40 | public override Type Type() 41 | { 42 | return _type; 43 | } 44 | 45 | /// 46 | /// Not supported. 47 | /// 48 | public override MemberInfo MemberInfo => null; 49 | 50 | /// 51 | /// Always false. 52 | /// 53 | public override bool CanSetValue() 54 | { 55 | return false; 56 | } 57 | 58 | /// 59 | public override string ToString() 60 | { 61 | return _tostringCache ?? (_tostringCache = Name() + " | " + Object); 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Contents/ReadonlyListCacheEntry.cs: -------------------------------------------------------------------------------- 1 | using RuntimeUnityEditor.Core.Utils; 2 | 3 | namespace RuntimeUnityEditor.Core.Inspector.Entries 4 | { 5 | /// 6 | /// Represents a read-only cache entry in the inspector for list items. 7 | /// 8 | public class ReadonlyListCacheEntry : ReadonlyCacheEntry 9 | { 10 | /// 11 | public ReadonlyListCacheEntry(object o, int index) : base(GetListItemName(index), o) 12 | { 13 | } 14 | 15 | internal static string GetListItemName(int index) 16 | { 17 | return "Index: " + index; 18 | } 19 | 20 | /// 21 | public override string ToString() 22 | { 23 | var isNull = Object.IsNullOrDestroyedStr(); 24 | if (isNull != null) return "[" + isNull + "]"; 25 | 26 | return Object.ToString(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Inspector/InspectorStackEntryBase.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RuntimeUnityEditor.Core.Inspector.Entries 4 | { 5 | /// 6 | /// Base class for all inspector stack entries. 7 | /// 8 | public abstract class InspectorStackEntryBase 9 | { 10 | /// 11 | /// Constructor for the inspector stack entry. 12 | /// 13 | public InspectorStackEntryBase(string name) 14 | { 15 | Name = name; 16 | } 17 | 18 | private string _searchString = string.Empty; 19 | /// 20 | /// Search string for filtering the inspector stack entries. 21 | /// 22 | public string SearchString 23 | { 24 | get => _searchString; 25 | // The string can't be null under unity 5.x or we crash 26 | set => _searchString = value ?? string.Empty; 27 | } 28 | 29 | /// 30 | /// Name of the entry. 31 | /// 32 | public string Name { get; } 33 | 34 | /// 35 | /// If the entry can be displayed in the inspector. 36 | /// 37 | public abstract bool EntryIsValid(); 38 | 39 | /// 40 | public override string ToString() 41 | { 42 | return Name; 43 | } 44 | 45 | /// 46 | /// The position of the scroll view in the inspector. 47 | /// 48 | public Vector2 ScrollPosition; 49 | 50 | /// 51 | /// Open context menu for this entry. 52 | /// 53 | public abstract void ShowContextMenu(); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Inspector/InstanceStackEntry.cs: -------------------------------------------------------------------------------- 1 | namespace RuntimeUnityEditor.Core.Inspector.Entries { 2 | /// 3 | /// Represents an entry in the inspector stack that holds an instance of an object. 4 | /// 5 | public class InstanceStackEntry : InspectorStackEntryBase 6 | { 7 | /// 8 | public InstanceStackEntry(object instance, string name) : this(instance, name, null) { } 9 | /// 10 | public InstanceStackEntry(object instance, string name, ICacheEntry parent) : base(name) 11 | { 12 | Instance = instance; 13 | Parent = parent; 14 | } 15 | 16 | /// 17 | /// The instance of the object represented by this entry. 18 | /// 19 | public object Instance { get; } 20 | 21 | /// 22 | /// The parent entry of this instance, if any. 23 | /// 24 | public ICacheEntry Parent { get; } 25 | 26 | /// 27 | public override bool EntryIsValid() 28 | { 29 | return Instance != null; 30 | } 31 | 32 | /// 33 | public override void ShowContextMenu() 34 | { 35 | ContextMenu.Instance.Show(Instance, Parent); 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/Entries/Inspector/StaticStackEntry.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace RuntimeUnityEditor.Core.Inspector.Entries 4 | { 5 | /// 6 | /// Represents an entry in the inspector stack that holds a static type. 7 | /// 8 | public class StaticStackEntry : InspectorStackEntryBase 9 | { 10 | /// 11 | public StaticStackEntry(Type staticType, string name) : base(name) 12 | { 13 | StaticType = staticType; 14 | } 15 | 16 | /// 17 | /// The static type represented by this entry. 18 | /// 19 | public Type StaticType { get; } 20 | 21 | /// 22 | public override bool EntryIsValid() 23 | { 24 | return StaticType != null; 25 | } 26 | 27 | /// 28 | public override void ShowContextMenu() 29 | { 30 | ContextMenu.Instance.Show(StaticType); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Inspector/InspectorHelpObject.cs: -------------------------------------------------------------------------------- 1 | using RuntimeUnityEditor.Core.Inspector.Entries; 2 | 3 | namespace RuntimeUnityEditor.Core.Inspector 4 | { 5 | internal class InspectorHelpObject 6 | { 7 | private InspectorHelpObject() 8 | { } 9 | 10 | public static InspectorStackEntryBase Create() 11 | { 12 | var obj = new InspectorHelpObject(); 13 | return new InstanceStackEntry(obj, "Inspector Help"); 14 | } 15 | 16 | public string Help1 = "This window displays contents of game classes in real time. " + 17 | "You can go deeper by clicking on the buttons with member names on the left (here it's 'Help1'), " + 18 | "and in some cases you can edit the data (try clicking on this text and typing)." + 19 | "To apply changes you have to click outside of the field."; 20 | 21 | public string Help2 = "When you go deeper you can return to the original class by clicking on the history list above."; 22 | 23 | public string Help3 = "On the far left you can see the type that a member holds. In this case this is a string. Only some types can be edited." + 24 | "You can copy the value even if it can't be edited."; 25 | 26 | public string Help4 = "You can run methods by clicking their names on the list. Try clicking on GetHashCode below to run it. " + 27 | "Return value will appear. If a class is returned by the method click the method's name to open it (try S/Create method below)."; 28 | public string Help5 = "WARNING - Running methods arbitraliy can have unforeseen consequences! You might even crash the game in some cases!"; 29 | 30 | public string Help6 = "If something goes wrong while getting a member's value, you will see an EXCEPTION or ERROR message in the value field." + 31 | "Usually you can click on the member's name again to view details of the error."; 32 | 33 | public string Help7 = "Above you can find various objects to display in the inspector. " + 34 | "WARNING - If you search a heavily populated scene, you will get A LOT of results. Your FPS will drop, that's normal."; 35 | 36 | public string Help8 = "'IS ENUMERABLE' means that opening the member will give you anywhere from 0 to infinitely many values. They might even be generated as you view them. " + 37 | "If the number of values is known, it will be displayed instead of this text."; 38 | 39 | public string Help9 = "If REPL is supported (the C# command prompt), you can pull and push object to and from inspector by using the 'geti()' and 'set(obj)' commands. Type 'help' in REPL for more info."; 40 | 41 | public static string HelpS = "If a member name has an S/ in front of it, it means that this is a static member. It will be the same in all instances of an object."; 42 | 43 | public string HelpTabs = "Right click on any member to open it in a tab. Right click a tab to close it."; 44 | } 45 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/ObjectView/ObjectViewWindow.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using HarmonyLib; 5 | using RuntimeUnityEditor.Core.Utils; 6 | using RuntimeUnityEditor.Core.Utils.Abstractions; 7 | using UnityEngine; 8 | 9 | namespace RuntimeUnityEditor.Core.ObjectView 10 | { 11 | /// 12 | /// Displays a friendly representation of objects, e.g. textures. 13 | /// 14 | public sealed class ObjectViewWindow : Window 15 | { 16 | private object _objToDisplay; 17 | private Action _objDrawer; 18 | 19 | private Vector2 _scrollPos; 20 | 21 | private static Dictionary> _objectDrawers = new Dictionary> 22 | { 23 | {typeof(Texture), o => GUILayout.Label((Texture)o, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true))}, 24 | {typeof(GUIContent), o => GUILayout.Label((GUIContent)o, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true))}, 25 | {typeof(string), o => GUILayout.TextArea((string)o, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true))}, 26 | }; 27 | 28 | private static bool GetDrawer(object objToDisplay, out Action drawer) 29 | { 30 | if (objToDisplay == null) 31 | { 32 | drawer = o => GUILayout.Label("No object selected.\n\nYou can send objects here from other windows by clicking the \"Preview\" or \"V\" buttons."); 33 | return false; 34 | } 35 | 36 | if (objToDisplay is UnityEngine.Object uo && !uo) 37 | { 38 | drawer = o => GUILayout.Label($"Selected Unity Object of type [{uo.GetType().GetFancyDescription()}] has been destroyed."); 39 | return false; 40 | } 41 | 42 | var objType = objToDisplay.GetType(); 43 | if (_objectDrawers.TryGetValue(objType, out drawer)) 44 | return true; 45 | 46 | drawer = _objectDrawers.FirstOrDefault(x => x.Key.IsAssignableFrom(objType)).Value; 47 | if (drawer != null) 48 | return true; 49 | 50 | drawer = o => GUILayout.Label($"Unsupported object type: {objToDisplay?.GetType()}"); 51 | return false; 52 | } 53 | 54 | /// 55 | /// Can a given object be displayed. 56 | /// 57 | public bool CanPreview(object obj) 58 | { 59 | return GetDrawer(obj, out _); 60 | } 61 | 62 | /// 63 | /// Set object to display and its name to show in the title bar. 64 | /// 65 | public void SetShownObject(object objToDisplay, string objName) 66 | { 67 | _objToDisplay = objToDisplay; 68 | GetDrawer(objToDisplay, out _objDrawer); 69 | 70 | _scrollPos = Vector2.zero; 71 | 72 | Title = "Object viewer - " + (objName ?? objToDisplay?.GetType().FullDescription() ?? "NULL"); 73 | 74 | Enabled = true; 75 | } 76 | 77 | /// 78 | protected override void DrawContents() 79 | { 80 | if (_objDrawer == null) GetDrawer(_objToDisplay, out _objDrawer); 81 | 82 | GUILayout.BeginVertical(); 83 | { 84 | GUILayout.BeginHorizontal(); 85 | { 86 | ContextMenu.Instance.DrawContextButton(_objToDisplay, null); 87 | } 88 | GUILayout.EndHorizontal(); 89 | 90 | _scrollPos = GUILayout.BeginScrollView(_scrollPos, true, true, GUILayout.ExpandWidth(true), GUILayout.ExpandHeight(true)); 91 | { 92 | _objDrawer(_objToDisplay); 93 | } 94 | GUILayout.EndScrollView(); 95 | } 96 | GUILayout.EndVertical(); 97 | } 98 | 99 | /// 100 | protected override void Initialize(InitSettings initSettings) 101 | { 102 | Title = "Object viewer - Empty"; 103 | DisplayName = "Viewer"; 104 | DefaultScreenPosition = ScreenPartition.LeftUpper; 105 | } 106 | } 107 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/REPL/MCS/ScriptEvaluator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Reflection; 5 | using Mono.CSharp; 6 | 7 | namespace RuntimeUnityEditor.Core.REPL.MCS 8 | { 9 | internal class ScriptEvaluator : Evaluator, IDisposable 10 | { 11 | private static readonly HashSet StdLib = 12 | new HashSet(StringComparer.InvariantCultureIgnoreCase) {"mscorlib", "System.Core", "System", "System.Xml"}; 13 | 14 | private readonly TextWriter _logger; 15 | 16 | public ScriptEvaluator(TextWriter logger) : base(BuildContext(logger)) 17 | { 18 | _logger = logger; 19 | 20 | ImportAppdomainAssemblies(ReferenceAssembly); 21 | AppDomain.CurrentDomain.AssemblyLoad += OnAssemblyLoad; 22 | } 23 | 24 | public void Dispose() 25 | { 26 | AppDomain.CurrentDomain.AssemblyLoad -= OnAssemblyLoad; 27 | _logger.Dispose(); 28 | } 29 | 30 | private void OnAssemblyLoad(object sender, AssemblyLoadEventArgs args) 31 | { 32 | string name = args.LoadedAssembly.GetName().Name; 33 | if (StdLib.Contains(name)) 34 | return; 35 | ReferenceAssembly(args.LoadedAssembly); 36 | } 37 | 38 | private static CompilerContext BuildContext(TextWriter tw) 39 | { 40 | var reporter = new StreamReportPrinter(tw); 41 | 42 | var settings = new CompilerSettings 43 | { 44 | Version = LanguageVersion.Experimental, 45 | GenerateDebugInfo = false, 46 | StdLib = true, 47 | Target = Target.Library, 48 | WarningLevel = 0, 49 | EnhancedWarnings = false 50 | }; 51 | 52 | return new CompilerContext(settings, reporter); 53 | } 54 | 55 | private static void ImportAppdomainAssemblies(Action import) 56 | { 57 | foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) 58 | { 59 | string name = assembly.GetName().Name; 60 | if (StdLib.Contains(name)) 61 | continue; 62 | import(assembly); 63 | } 64 | } 65 | } 66 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/REPL/ReplHelper.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using UnityEngine; 3 | 4 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 5 | 6 | namespace RuntimeUnityEditor.Core.REPL 7 | { 8 | //todo redundant? 9 | public class ReplHelper : MonoBehaviour 10 | { 11 | public T Find() where T : Object 12 | { 13 | return FindObjectOfType(); 14 | } 15 | 16 | public T[] FindAll() where T : Object 17 | { 18 | return FindObjectsOfType(); 19 | } 20 | 21 | public Coroutine RunCoroutine(IEnumerator i) 22 | { 23 | return this.AbstractStartCoroutine(i); 24 | } 25 | 26 | public void EndCoroutine(Coroutine c) 27 | { 28 | StopCoroutine(c); 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/REPL/Suggestion.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | 3 | namespace RuntimeUnityEditor.Core.REPL { 4 | internal struct Suggestion 5 | { 6 | public readonly string Original; 7 | public readonly string Result; 8 | public Suggestion(string result, string original, SuggestionKind kind) 9 | { 10 | Original = original; 11 | Kind = kind; 12 | Result = result; 13 | } 14 | 15 | public readonly SuggestionKind Kind; 16 | 17 | public Color GetTextColor() 18 | { 19 | return Kind == SuggestionKind.Namespace ? Color.gray : Color.white; 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/REPL/SuggestionKind.cs: -------------------------------------------------------------------------------- 1 | namespace RuntimeUnityEditor.Core.REPL { 2 | internal enum SuggestionKind 3 | { 4 | Unknown, 5 | Namespace 6 | } 7 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/REPL/TypeHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Reflection; 3 | using System.Text; 4 | using RuntimeUnityEditor.Core.Utils; 5 | 6 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 7 | 8 | namespace RuntimeUnityEditor.Core.REPL 9 | { 10 | /// 11 | /// Only for internal use. 12 | /// 13 | public class TypeHelper 14 | { 15 | public object instance; 16 | public Type type; 17 | 18 | public TypeHelper(Type type) 19 | { 20 | this.type = type; 21 | instance = null; 22 | } 23 | 24 | public TypeHelper(object instance) 25 | { 26 | this.instance = instance; 27 | type = instance.GetType(); 28 | } 29 | 30 | public T val(string name) where T : class 31 | { 32 | var field = type.GetField(name, 33 | BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public 34 | | BindingFlags.NonPublic); 35 | 36 | if (field != null) 37 | { 38 | if (!field.IsStatic && instance == null) 39 | throw new ArgumentException("Field is not static, but instance is missing."); 40 | return field.GetValue(field.IsStatic ? null : instance) as T; 41 | } 42 | 43 | var prop = type.GetProperty(name, 44 | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static 45 | | BindingFlags.Instance); 46 | 47 | if (prop == null || !prop.CanWrite) 48 | throw new ArgumentException($"No field or settable property of name {name} was found!"); 49 | 50 | var getter = prop.GetSetMethod(true); 51 | 52 | if (!getter.IsStatic && instance == null) 53 | throw new ArgumentException("Property is not static, but instance is missing."); 54 | 55 | return getter.Invoke(getter.IsStatic ? null : instance, null) as T; 56 | } 57 | 58 | public void set(string name, object value) 59 | { 60 | var field = type.GetField(name, 61 | BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public 62 | | BindingFlags.NonPublic); 63 | 64 | if (field != null) 65 | { 66 | if (!field.IsStatic && instance == null) 67 | throw new ArgumentException("Field is not static, but instance is missing."); 68 | field.SetValue(field.IsStatic ? null : instance, value); 69 | return; 70 | } 71 | 72 | var prop = type.GetProperty(name, 73 | BindingFlags.IgnoreCase | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static 74 | | BindingFlags.Instance); 75 | 76 | if (prop == null || !prop.CanWrite) 77 | throw new ArgumentException($"No field or settable property of name {name} was found!"); 78 | 79 | var setter = prop.GetSetMethod(true); 80 | 81 | if (!setter.IsStatic && instance == null) 82 | throw new ArgumentException("Property is not static, but instance is missing."); 83 | 84 | setter.Invoke(setter.IsStatic ? null : instance, new[] { value }); 85 | } 86 | 87 | public object invoke(string name, params object[] args) 88 | { 89 | var method = type.GetMethod(name, 90 | BindingFlags.IgnoreCase | BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public 91 | | BindingFlags.NonPublic); 92 | if (method == null) 93 | throw new ArgumentException($"No method of name {name} was found!"); 94 | if (!method.IsStatic && instance == null) 95 | throw new ArgumentException("Method is not static, but instance is missing."); 96 | 97 | return method.Invoke(method.IsStatic ? null : instance, args); 98 | } 99 | 100 | public string info() 101 | { 102 | var sb = new StringBuilder(); 103 | 104 | sb.AppendLine($"Info about {type.GetSourceCodeRepresentation()}"); 105 | sb.AppendLine("Methods"); 106 | 107 | foreach (var methodInfo in type.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static 108 | | BindingFlags.Instance)) 109 | { 110 | bool putComma = false; 111 | sb.Append(methodInfo.IsPublic ? "public" : "private").Append(' '); 112 | if (methodInfo.ContainsGenericParameters) 113 | { 114 | sb.Append('<'); 115 | foreach (var genericArgument in methodInfo.GetGenericArgumentsSafe()) 116 | { 117 | if (putComma) 118 | sb.Append(", "); 119 | sb.Append(genericArgument.GetSourceCodeRepresentation()); 120 | putComma = true; 121 | } 122 | 123 | sb.Append('>'); 124 | } 125 | 126 | sb.Append(methodInfo.Name).Append('('); 127 | 128 | putComma = false; 129 | foreach (var parameterInfo in methodInfo.GetParameters()) 130 | { 131 | if (putComma) 132 | sb.Append(", "); 133 | sb.Append(parameterInfo.ParameterType.GetSourceCodeRepresentation()); 134 | if (parameterInfo.DefaultValue != DBNull.Value) 135 | sb.Append($"= {parameterInfo.DefaultValue}"); 136 | putComma = true; 137 | } 138 | 139 | sb.AppendLine(")"); 140 | } 141 | 142 | sb.AppendLine().AppendLine("Fields"); 143 | 144 | foreach (var fieldInfo in type.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance) 145 | ) 146 | { 147 | sb.Append(fieldInfo.IsPublic ? "public" : "private").Append(' '); 148 | sb.AppendLine(fieldInfo.Name); 149 | } 150 | 151 | return sb.ToString(); 152 | } 153 | } 154 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.Core/Windows/Taskbar.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Globalization; 3 | using System.Linq; 4 | using RuntimeUnityEditor.Core.Utils; 5 | using RuntimeUnityEditor.Core.Utils.Abstractions; 6 | using UnityEngine; 7 | 8 | namespace RuntimeUnityEditor.Core 9 | { 10 | /// 11 | /// Taskbar with controls for all of the RUE features. 12 | /// Avoid using this class directly, new features can be added with instead. 13 | /// 14 | public class Taskbar : IFeature 15 | { 16 | private int _windowId; 17 | private Rect _windowRect; 18 | private List _orderedFeatures; 19 | private string _title; 20 | 21 | /// 22 | /// Current instance. 23 | /// 24 | public static Taskbar Instance { get; private set; } 25 | 26 | /// 27 | /// Text shown in the title bar of the taskbar. 28 | /// 29 | protected string GetTitle() => RuntimeUnityEditorCore.Instance.ShowHotkey == KeyCode.None ? _title : _title + $" / Press {RuntimeUnityEditorCore.Instance.ShowHotkey} to show/hide"; 30 | 31 | /// 32 | /// Height of the taskbar. 33 | /// 34 | public int Height => (int)_windowRect.height; 35 | 36 | /// 37 | /// Do not create additional instances or things will break. 38 | /// This has to be public or things will break. 39 | /// 40 | public Taskbar() 41 | { 42 | Instance = this; 43 | } 44 | 45 | void IFeature.OnInitialize(InitSettings initSettings) 46 | { 47 | _windowId = GetHashCode(); 48 | _title = $"{RuntimeUnityEditorCore.GUID} v{RuntimeUnityEditorCore.Version}"; 49 | } 50 | 51 | /// 52 | /// Set features shown in the taskbar. 53 | /// 54 | public void SetFeatures(List initializedFeatures) 55 | { 56 | _orderedFeatures = initializedFeatures.OrderByDescending(x => x.DisplayType).ThenBy(x => x.DisplayName).ToList(); 57 | } 58 | 59 | void IFeature.OnOnGUI() 60 | { 61 | _windowRect = GUILayout.Window(_windowId, _windowRect, (GUI.WindowFunction)DrawTaskbar, GetTitle(), GUILayout.ExpandHeight(false), GUILayout.ExpandWidth(false), GUILayout.MaxWidth(Screen.width)); 62 | IMGUIUtils.EatInputInRect(_windowRect); 63 | _windowRect.x = (int)((Screen.width - _windowRect.width) / 2); 64 | _windowRect.y = (int)(Screen.height - _windowRect.height); 65 | } 66 | 67 | private void DrawTaskbar(int id) 68 | { 69 | GUILayout.BeginHorizontal(); 70 | 71 | var firstFeature = true; 72 | foreach (var feature in _orderedFeatures) 73 | { 74 | if (feature.DisplayType == FeatureDisplayType.Window) 75 | { 76 | GUI.color = feature.Enabled ? Color.cyan : Color.white; 77 | if (GUILayout.Button(feature.DisplayName)) 78 | feature.Enabled = !feature.Enabled; 79 | } 80 | else if (feature.DisplayType == FeatureDisplayType.Feature) 81 | { 82 | if (firstFeature) 83 | { 84 | GUI.color = new Color(1f, 1f, 1f, 0.75f); 85 | if (GUILayout.Button("Reset")) 86 | { 87 | // Needed to avoid WindowManager.ResetWindowRect using old rects as reference to where new windows should be placed. 88 | foreach (var window in _orderedFeatures.OfType()) 89 | window.WindowRect = new Rect(-1000, -1000, 0, 0); 90 | 91 | foreach (var window in _orderedFeatures.OfType().OrderBy(x => x.Title)) 92 | { 93 | // Ensure that all title bars are visible 94 | GUI.BringWindowToFront(window.WindowId); 95 | WindowManager.ResetWindowRect(window); 96 | } 97 | } 98 | firstFeature = false; 99 | GUI.color = Color.white; 100 | GUILayout.Label("|"); 101 | } 102 | feature.Enabled = GUILayout.Toggle(feature.Enabled, feature.DisplayName); 103 | } 104 | } 105 | 106 | GUILayout.Label("|"); 107 | 108 | GUILayout.Label("Time", IMGUIUtils.LayoutOptionsExpandWidthFalse); 109 | 110 | if (GUILayout.Button(">", IMGUIUtils.LayoutOptionsExpandWidthFalse)) 111 | Time.timeScale = 1; 112 | if (GUILayout.Button("||", IMGUIUtils.LayoutOptionsExpandWidthFalse)) 113 | Time.timeScale = 0; 114 | 115 | if (float.TryParse(GUILayout.TextField(Time.timeScale.ToString("F2", CultureInfo.InvariantCulture), GUILayout.Width(38)), NumberStyles.Any, CultureInfo.InvariantCulture, out var newVal)) 116 | Time.timeScale = newVal; 117 | 118 | GUI.changed = false; 119 | var n = GUILayout.Toggle(Application.runInBackground, "in BG"); 120 | if (GUI.changed) Application.runInBackground = n; 121 | 122 | GUILayout.Label("|"); 123 | 124 | if (GUILayout.Button("Log", IMGUIUtils.LayoutOptionsExpandWidthFalse)) 125 | UnityFeatureHelper.OpenLog(); 126 | 127 | AssetBundleManagerHelper.DrawButtonIfAvailable(); 128 | 129 | GUILayout.EndHorizontal(); 130 | } 131 | 132 | /// 133 | /// Show the taskbar, and by extension entire RUE interface. Use instead. 134 | /// 135 | public bool Enabled { get; set; } 136 | void IFeature.OnUpdate() { } 137 | void IFeature.OnLateUpdate() { } 138 | void IFeature.OnEditorShownChanged(bool visible) { } 139 | FeatureDisplayType IFeature.DisplayType => FeatureDisplayType.Hidden; 140 | string IFeature.DisplayName => "WindowManager"; 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.UMM/Info.json: -------------------------------------------------------------------------------- 1 | { 2 | "Id": "RuntimeUnityEditor", 3 | "DisplayName": "Runtime Unity Editor", 4 | "Author": "Vanguard, ManlyMarco", 5 | "Version": "0.0.0.0", 6 | "ManagerVersion": "0.27.2", 7 | "AssemblyName": "RuntimeUnityEditor.UMM.dll", 8 | "EntryMethod": "RuntimeUnityEditor.UMM.RuntimeUnityEditorUMM.Load", 9 | "Repository": "https://raw.githubusercontent.com/ManlyMarco/RuntimeUnityEditor/master/RuntimeUnityEditor.UMM/Repository.json", 10 | "HomePage": "https://github.com/ManlyMarco/RuntimeUnityEditor" 11 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.UMM/Repository.json: -------------------------------------------------------------------------------- 1 | { 2 | "Releases": [ 3 | { 4 | "Id": "RuntimeUnityEditor", 5 | "Version": "5.0.0", 6 | "DownloadUrl": "https://github.com/ManlyMarco/RuntimeUnityEditor/releases/download/v5.0/RuntimeUnityEditor_UMM_v5.0.zip" 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /RuntimeUnityEditor.UMM/RuntimeUnityEditor.UMM.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net35 5 | Library 6 | 7 | ..\bin\UMM\ 8 | false 9 | 10 | 11 | 12 | true 13 | True 14 | 15 | 16 | 17 | 18 | PreserveNewest 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | all 33 | compile 34 | 35 | 36 | 37 | 38 | 39 | all 40 | compile 41 | 42 | 43 | 44 | 45 | 46 | 47 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /RuntimeUnityEditor.UMM/RuntimeUnityEditorUMM.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using RuntimeUnityEditor.Core; 3 | using RuntimeUnityEditor.Core.Utils.Abstractions; 4 | using UnityEngine; 5 | using UnityModManagerNet; 6 | 7 | #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member 8 | #pragma warning disable IDE0051 // Remove unused private members 9 | 10 | namespace RuntimeUnityEditor.UMM 11 | { 12 | /// 13 | /// This is a loader plugin for UnityModManager. 14 | /// When referencing RuntimeUnityEditor from other code it's recommended to not reference this assembly and instead reference RuntimeUnityEditorCore directly. 15 | /// You can see if RuntimeUnityEditor has finished loading with RuntimeUnityEditorCore.IsInitialized(). 16 | /// 17 | [Obsolete("It's recommended to reference RuntimeUnityEditorCore directly")] 18 | public static class RuntimeUnityEditorUMM 19 | { 20 | public static bool Enabled 21 | { 22 | get => _editorMonoBehaviour?.enabled ?? false; 23 | private set => _editorMonoBehaviour.enabled = value; 24 | } 25 | public static UnityModManager.ModEntry Instance { get; private set; } 26 | public static RuntimeUnityEditorCore CoreInstance { get; private set; } 27 | public static RuntimeUnityEditorSettings Settings { get; private set; } 28 | 29 | private static GameObject _editorGameObject; 30 | private static MonoBehaviour _editorMonoBehaviour; 31 | 32 | public static bool Load(UnityModManager.ModEntry modEntry) 33 | { 34 | if (Instance != null) 35 | { 36 | Instance.Logger.Critical("Multiple copies of Runtime Unity Editor detected!"); 37 | return false; 38 | } 39 | 40 | Instance = modEntry; 41 | 42 | Settings = UnityModManager.ModSettings.Load(Instance); 43 | Settings.Load(Instance); 44 | 45 | _editorGameObject = new GameObject("RuntimeUnityEditor", typeof(RuntimeUnityEditorBehaviour)); 46 | UnityEngine.Object.DontDestroyOnLoad(_editorGameObject); 47 | 48 | Instance.OnGUI = OnGUI; 49 | Instance.OnSaveGUI = Settings.Save; 50 | 51 | return true; 52 | } 53 | 54 | private static void OnGUI(UnityModManager.ModEntry modEntry) 55 | { 56 | Settings.Draw(modEntry); 57 | } 58 | 59 | private class RuntimeUnityEditorBehaviour : MonoBehaviour 60 | { 61 | private void Start() 62 | { 63 | _editorMonoBehaviour = this; 64 | CoreInstance = new RuntimeUnityEditorCore(new UMMInitSettings(Instance, Settings)); 65 | } 66 | 67 | private void Update() => CoreInstance.Update(); 68 | 69 | private void LateUpdate() => CoreInstance.LateUpdate(); 70 | 71 | private void OnGUI() => CoreInstance.OnGUI(); 72 | } 73 | 74 | private sealed class UMMInitSettings : InitSettings 75 | { 76 | private readonly RuntimeUnityEditorSettings _settings; 77 | 78 | public UMMInitSettings(UnityModManager.ModEntry instance, RuntimeUnityEditorSettings settings) 79 | { 80 | _settings = settings; 81 | LoggerWrapper = new LoggerUMM(instance.Logger); 82 | } 83 | 84 | protected override Action RegisterSetting(string category, string name, T defaultValue, string description, Action onValueUpdated) 85 | { 86 | if (!_settings.Get(category, name, out var setting)) 87 | { 88 | setting = new RuntimeUnityEditorSettings.Setting(defaultValue); 89 | _settings.Add(category, name, setting); 90 | } 91 | setting.OnChanged = onValueUpdated; 92 | onValueUpdated(setting.Value); 93 | 94 | return (newValue) => { setting.Value = newValue; onValueUpdated(newValue); }; 95 | } 96 | 97 | public override MonoBehaviour PluginMonoBehaviour => _editorMonoBehaviour; 98 | public override ILoggerWrapper LoggerWrapper { get; } 99 | public override string ConfigPath => Instance.Path; 100 | } 101 | 102 | private sealed class LoggerUMM : ILoggerWrapper 103 | { 104 | private readonly UnityModManager.ModEntry.ModLogger _logger; 105 | 106 | public LoggerUMM(UnityModManager.ModEntry.ModLogger logger) 107 | { 108 | _logger = logger; 109 | } 110 | 111 | public void Log(Core.Utils.Abstractions.LogLevel logLevel, object content) 112 | { 113 | switch (logLevel) 114 | { 115 | case LogLevel.All: 116 | case LogLevel.None: 117 | case LogLevel.Fatal: 118 | if (content is Exception e1) _logger.LogException(e1); 119 | else _logger.Critical(content as string); 120 | break; 121 | 122 | case LogLevel.Error: 123 | if (content is Exception e2) _logger.LogException(e2); 124 | else _logger.Error(content as string); 125 | break; 126 | 127 | case LogLevel.Warning: 128 | if (content is Exception e3) _logger.LogException(e3); 129 | else _logger.Warning(content as string); 130 | break; 131 | 132 | case LogLevel.Message: 133 | case LogLevel.Info: 134 | case LogLevel.Debug: 135 | if (content is Exception e4) _logger.LogException(e4); 136 | else _logger.Log(content as string); 137 | break; 138 | 139 | default: 140 | _logger.Warning($"Unknown LogLevel [{logLevel}] for content: {content}"); 141 | break; 142 | } 143 | } 144 | } 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /install.ps1: -------------------------------------------------------------------------------- 1 | param($installPath, $toolsPath, $package, $project) 2 | $asms = $package.AssemblyReferences | %{$_.Name} 3 | foreach ($reference in $project.Object.References) 4 | { 5 | if ($asms -contains $reference.Name + ".dll") 6 | { 7 | $reference.CopyLocal = $false; 8 | } 9 | } -------------------------------------------------------------------------------- /modules/Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | --------------------------------------------------------------------------------