Compare commits

...

57 Commits
3.0.3 ... 3.2.3

Author SHA1 Message Date
1c0011bef9 bump version 2021-03-16 18:13:14 +11:00
9e996816ef starting work on a cleanup/rewrite 2021-03-16 18:12:39 +11:00
9665753dc8 fix BIE5 mono release 2021-03-16 18:12:26 +11:00
942e9d7555 some commenting 2021-03-14 16:08:53 +11:00
9efb9581f5 Cleanups, method summaries, bump version 2021-03-12 18:41:57 +11:00
f10a462b00 More reflection caching, use deobfuscated names for ToString labels 2021-03-12 18:41:38 +11:00
9072b16c5a Save window size between launches 2021-03-12 18:39:57 +11:00
21408993c2 create subfolder for standalone 2021-03-11 18:40:04 +11:00
7a8b5b50d1 bump version, update readme 2021-03-11 18:32:57 +11:00
1a5e843070 handle ExplorerStandalone update internally 2021-03-11 18:16:52 +11:00
ade7539fde cleanups 2021-03-11 17:57:58 +11:00
5c588e5a03 Update README.md 2021-03-10 05:04:43 +11:00
af094832fe Update README.md 2021-03-09 22:31:26 +11:00
af0ee2e690 Update README.md 2021-03-09 22:29:53 +11:00
d2d6fb4d55 Update README.md 2021-03-09 22:28:43 +11:00
6c25662fe9 Update README.md 2021-03-09 17:36:49 +11:00
4bcf82ca10 separate Bep5 and Bep6 support. Fix enums not backed by ints. 2021-03-09 17:35:54 +11:00
ce38e8ac50 bump to MelonLoader 0.3.0 2021-03-06 17:25:54 +11:00
12cd718f12 3.1.12: store Il2CppToMonoType key as string AssemblyQualifiedName instead of Type object 2021-03-04 01:44:34 +11:00
995e2a3e93 3.1.11: fix potential crash on scene reload 2021-03-03 22:09:14 +11:00
2c95fec646 3.1.10: Add "Hide on startup" config option 2021-02-27 17:04:47 +11:00
69912d7ea4 Prevent GC Mark Overflow on C# Console copy+paste 2021-02-26 17:54:00 +11:00
9c5596ace4 Update lib versions 2021-02-26 17:52:21 +11:00
d4dac58fc8 Fix for deobfuscated unhollowed types not being properly resolved 2021-02-20 19:39:19 +11:00
77b97cbe17 Update README.md 2021-02-03 18:31:29 +11:00
c6f0f34ac0 Update README.md 2021-01-28 02:09:40 +11:00
d1f4f74d32 some ui cleanups (minor) 2021-01-22 21:56:00 +11:00
f13068bf01 Update README.md 2021-01-20 23:05:04 +11:00
dfc288a101 Update README.md 2021-01-20 19:10:13 +11:00
544009dc21 3.1.7
* Added standalone release build (thanks @Alloc86)
* Improved formatting for ToString methods which accept an IFormatProvider
* When editing a struct, the reference to the parent member will now be updated if you modify the struct values.
2021-01-20 17:22:36 +11:00
fdfaaadd89 3.1.6 - don't bother setting pixelPerfect on canvas 2021-01-14 17:46:32 +11:00
58d60a10d4 Update ForceUnlockCursor.cs 2021-01-04 01:23:20 +11:00
0432c6d56c 3.1.5
* Integrate PR from js6pak
2021-01-03 19:27:02 +11:00
8c34aa2be5 Merge pull request #29 from js6pak/embedded-assetbundle
Load assetbundle from EmbeddedResource
2021-01-03 19:13:15 +11:00
4a1c54fac1 Load assetbundle from EmbeddedResource 2021-01-02 19:38:01 +01:00
190467fa5c Update README.md 2020-12-31 18:34:26 +11:00
44f54d9190 3.1.4 2020-12-31 18:32:52 +11:00
3b4ea31b50 Fix Texture2D saver in Mono and for non-readable textures 2020-12-24 18:10:17 +11:00
ad7b05f721 Just disable EventSystem component and not the entire gameobject 2020-12-21 16:33:34 +11:00
852ca8e9eb New attempt at fixing conflicting EventSystem problems in IL2CPP 2020-12-16 14:28:54 +11:00
7386eca0c2 Update UIManager.cs 2020-12-15 19:33:04 +11:00
97325a5f3a Fix an issue causing duplicated clicks in some IL2CPP games, fix setting Component.enabled in IL2CPP 2020-12-15 19:32:50 +11:00
82e52de557 Cleanup and fix Singleton search slightly 2020-12-14 19:26:59 +11:00
28181e2266 Restoring Texture viewer/saver, and Static/Singleton class searching 2020-12-14 18:35:43 +11:00
6dfa4806ce 3.0.8
Reverting to the previous World-Raycast method as it gave more accurate/expected results
2020-12-12 23:24:44 +11:00
27f6a6ca52 Update README.md 2020-12-10 03:21:22 +11:00
cd7b260ea7 Fix in issue where the Behaviour Enabled toggle doesn't work in IL2CPP 2020-12-08 19:42:44 +11:00
ba986274be Bump version 2020-12-07 22:22:25 +11:00
d181c0bee9 Improved Enumerable and Dictionary enumeration in IL2CPP 2020-12-07 22:22:03 +11:00
a9faec8cf9 Some cleanups 2020-12-03 22:12:30 +11:00
a6bf91b7af accidentally replaced the original asset bundle 2020-11-25 16:47:13 +11:00
e7c5170232 3.0.5 2020-11-25 16:41:42 +11:00
be635e46a0 Swapping to INI instead of XML config, including an AssetBundle for old (5.6.1) Unity versions. 2020-11-25 16:40:36 +11:00
687f56eac9 Update README.md 2020-11-23 21:16:29 +11:00
1dfcdb2dca Update build instructions, remove unnecessary references from CS project file 2020-11-23 20:28:22 +11:00
0025c83930 Fix a preprocessor directive 2020-11-23 20:17:54 +11:00
cfa4b12039 3.0.4
see release notes
2020-11-23 18:23:25 +11:00
61 changed files with 2335 additions and 1126 deletions

View File

@ -17,7 +17,6 @@
- [Features](#features)
- [How to install](#how-to-install)
- [Mod Config](#mod-config)
- [Mouse Control](#mouse-control)
- [Building](#building)
- [Credits](#credits)
@ -25,11 +24,12 @@
| Mod Loader | IL2CPP | Mono |
| ----------- | ------ | ---- |
| [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) | ✔️ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Il2Cpp.zip) | ✔️ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Mono.zip) |
| [BepInEx](https://github.com/BepInEx/BepInEx) | ✔️ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx.Il2Cpp.zip) | ✔️ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx.Mono.zip) |
| [BepInEx](https://github.com/BepInEx/BepInEx) 6.X | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx.Il2Cpp.zip) | ❔* [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx6.Mono.zip) |
| [BepInEx](https://github.com/BepInEx/BepInEx) 5.X | ❌ n/a | [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.BepInEx5.Mono.zip) |
| [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) 0.3 | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.MelonLoader.Mono.zip) |
| Standalone | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.Standalone.Il2Cpp.zip) | ✅ [link](https://github.com/sinai-dev/UnityExplorer/releases/latest/download/UnityExplorer.Standalone.Mono.zip) |
<b>IL2CPP Issues:</b>
* Some methods may still fail with a `MissingMethodException`, please let me know if you experience this (with full debug log please).
\* BepInEx 6.X Mono release may not work on all games yet.
## Features
@ -44,27 +44,44 @@
* <b>Reflection Inspector</b>: Inspect Properties and Fields. Can also set primitive values and evaluate primitive methods.
* <b>Search</b>: Search for UnityEngine.Objects with various filters, or use the helpers for static Instances and Classes.
* <b>C# Console</b>: Interactive console for evaluating C# methods on the fly, with some basic helpers.
* <b>Inspect-under-mouse</b>: Hover over an object with a collider and inspect it by clicking on it.
* <b>Inspect-under-mouse</b>: Hover over an object with a collider and inspect it by clicking on it. There's also a UI mode to inspect UI objects.
## How to install
### BepInEx
Note: For IL2CPP you should use [BepInEx 6 (Bleeding Edge)](https://builds.bepis.io/projects/bepinex_be), for Mono you should use [BepInEx 5](https://github.com/BepInEx/BepInEx/releases) (until Mono support stabilizes in BepInEx 6).
0. Install [BepInEx](https://github.com/BepInEx/BepInEx) for your game.
1. Download the UnityExplorer release for BepInEx IL2CPP or Mono above.
2. Take the `UnityExplorer.dll` file and put it in `[GameFolder]\BepInEx\plugins\`
3. Take the `UnityExplorer\` folder (with `explorerui.bundle`) and put it in `[GameFolder]\Mods\`, so it looks like `[GameFolder]\Mods\UnityExplorer\explorerui.bundle`.
4. In IL2CPP, it is highly recommended to get the base Unity libs for the game's Unity version and put them in the `BepInEx\unhollowed\base\` folder.
2. Take the `UnityExplorer.BIE.___.dll` file and put it in `[GameFolder]\BepInEx\plugins\`
3. In IL2CPP, it is highly recommended to get the base Unity libs for the game's Unity version and put them in the `BepInEx\unity-libs\` folder.
### MelonLoader
Note: You must use version 0.3 of MelonLoader or greater. Version 0.3 is currently in pre-release, so you must opt-in from your MelonLoader installer (enable alpha releases).
0. Install [MelonLoader](https://github.com/HerpDerpinstine/MelonLoader) for your game.
1. Download the UnityExplorer release for MelonLoader IL2CPP or Mono above.
2. Take the contents of the release and put it in the `[GameFolder]\Mods\` folder. It should look like `[GameFolder]\Mods\UnityExplorer.dll` and `[GameFolder]\Mods\UnityExplorer\explorerui.bundle`.
2. Take the contents of the release and put it in the `[GameFolder]\Mods\` folder. It should look like `[GameFolder]\Mods\UnityExplorer.ML.___.dll`
## Mod Config
### Standalone
You can access the settings via the "Options" page of the main menu, or directly from the config at `Mods\UnityExplorer\config.xml` (generated after first launch).
The standalone release is based on the BepInEx build, so it requires Harmony 2.0 (or HarmonyX) to function properly.
0. Load the DLL from your mod or inject it. You must also make sure that the required libraries (Harmony, Unhollower for Il2Cpp, etc) are loaded.
1. Create an instance of Unity Explorer with `ExplorerStandalone.CreateInstance();`
2. Optionally subscribe to the `ExplorerStandalone.OnLog` event to handle logging if you wish.
## Logging
Explorer saves all logs to disk (only keeps the most recent 10 logs). They can be found in a "UnityExplorer" folder in the same place as where you put the DLL file.
These logs are also visible in the Debug Console part of the UI.
## Settings
You can change the settings via the "Options" page of the main menu, or directly from the config file (generated after first launch). The config file will be found either inside a "UnityExplorer" folder in the same directory as where you put the DLL file, or for BepInEx it will be at `BepInEx\config\UnityExplorer\`.
`Main Menu Toggle` (KeyCode)
* Default: `F7`
@ -80,25 +97,32 @@ You can access the settings via the "Options" page of the main menu, or directly
* <b>Requires a restart to take effect</b>, apart from Reflection Inspector tabs.
`Default Output Path` (string)
* Default: `Mods\Explorer`
* Default: `Mods\UnityExplorer`
* Where output is generated to, by default (for Texture PNG saving, etc).
* Currently this is not actually used for anything, but it will be soon.
`Log Unity Debug` (bool)
* Default: `false`
* Listens for Unity `Debug.Log` messages and prints them to UnityExplorer's log.
`Hide on Startup` (bool)
* Default: `false`
* If true, UnityExplorer will be hidden when you start the game, you must open it via the keybind.
## Building
If you'd like to build this yourself, you will need to have installed BepInEx and/or MelonLoader for at least one Unity game. If you want to build all 4 versions, you will need at least one IL2CPP and one Mono game, with BepInEx and MelonLoader installed for both.
If you'd like to build this yourself, all you need to do is download this repository and build from Visual Studio. If you want to build for BepInEx or MelonLoader IL2CPP then you will need to install the mod loader for a game and set the directory in the `csproj` file.
1. Install MelonLoader or BepInEx for your game.
2. Open the `src\Explorer.csproj` file in a text editor.
3. Set the relevant `GameFolder` values for the versions you want to build, eg. set `MLCppGameFolder` if you want to build for a MelonLoader IL2CPP game.
4. Open the `src\Explorer.sln` project.
5. Select `Solution 'UnityExplorer' (1 of 1 project)` in the Solution Explorer panel, and set the <b>Active config</b> property to the version you want to build, then build it.
5. The DLLs are built to the `Release\` folder in the root of the repository.
6. If ILRepack fails or is missing, use the NuGet package manager to re-install `ILRepack.Lib.MSBuild.Task`, then re-build.
For IL2CPP:
1. Install BepInEx or MelonLoader for your game.
2. Open the `src\UnityExplorer.csproj` file in a text editor.
3. Set `BIECppGameFolder` (for BepInEx) and/or `MLCppGameFolder` (for MelonLoader) so the project can locate the necessary references.
4. For Standalone builds, you can either install BepInEx for the game to build, or just change the .csproj file and set the Unhollower reference manually.
For all builds:
1. Open the `src\UnityExplorer.sln` project.
2. Select `Solution 'UnityExplorer' (1 of 1 project)` in the Solution Explorer panel, and set the <b>Active config</b> property to the version you want to build, then build it.
3. The DLLs are built to the `Release\` folder in the root of the repository.
4. If ILRepack fails or is missing, use the NuGet package manager to re-install `ILRepack.Lib.MSBuild.Task`, then re-build.
## Credits

Binary file not shown.

BIN
lib/BepInEx.Unity.dll Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/INIFileParser.dll Normal file

Binary file not shown.

Binary file not shown.

BIN
lib/MelonLoader.dll Normal file

Binary file not shown.

View File

@ -32,7 +32,7 @@ namespace UnityExplorer.CSConsole
private Text inputHighlightText;
private readonly CSharpLexer highlightLexer;
private readonly StringBuilder sbHighlight;
//private readonly StringBuilder sbHighlight;
internal int m_lastCaretPos;
internal int m_fixCaretPos;
@ -68,7 +68,6 @@ The following helper methods are available:
public CodeEditor()
{
sbHighlight = new StringBuilder();
highlightLexer = new CSharpLexer();
ConstructUI();
@ -76,8 +75,24 @@ The following helper methods are available:
InputField.onValueChanged.AddListener((string s) => { OnInputChanged(s); });
}
internal static bool IsUserCopyPasting()
{
return (InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
&& InputManager.GetKeyDown(KeyCode.V);
}
public void Update()
{
if (s_copyPasteBuffer != null)
{
if (!IsUserCopyPasting())
{
OnInputChanged(s_copyPasteBuffer);
s_copyPasteBuffer = null;
}
}
if (EnableCtrlRShortcut)
{
if ((InputManager.GetKey(KeyCode.LeftControl) || InputManager.GetKey(KeyCode.RightControl))
@ -149,11 +164,18 @@ The following helper methods are available:
AutoCompleter.ClearAutocompletes();
}
public void OnInputChanged(string newInput, bool forceUpdate = false)
{
string newText = newInput;
internal static string s_copyPasteBuffer;
UpdateIndent(newInput);
public void OnInputChanged(string newText, bool forceUpdate = false)
{
if (IsUserCopyPasting())
{
//Console.WriteLine("Copy+Paste detected!");
s_copyPasteBuffer = newText;
return;
}
UpdateIndent(newText);
if (!forceUpdate && string.IsNullOrEmpty(newText))
inputHighlightText.text = string.Empty;
@ -203,35 +225,29 @@ The following helper methods are available:
{
int offset = 0;
sbHighlight.Length = 0;
//Console.WriteLine("Highlighting input text:\r\n" + inputText);
string ret = "";
foreach (LexerMatchInfo match in highlightLexer.GetMatches(inputText))
{
for (int i = offset; i < match.startIndex; i++)
{
sbHighlight.Append(inputText[i]);
}
ret += inputText[i];
sbHighlight.Append($"{match.htmlColor}");
ret += $"{match.htmlColor}";
for (int i = match.startIndex; i < match.endIndex; i++)
{
sbHighlight.Append(inputText[i]);
}
ret += inputText[i];
sbHighlight.Append(CLOSE_COLOR_TAG);
ret += CLOSE_COLOR_TAG;
offset = match.endIndex;
}
for (int i = offset; i < inputText.Length; i++)
{
sbHighlight.Append(inputText[i]);
}
ret += inputText[i];
inputText = sbHighlight.ToString();
return inputText;
return ret;
}
private void AutoIndentCaret()

View File

@ -0,0 +1,153 @@
using System;
using System.IO;
using UnityEngine;
using IniParser;
using IniParser.Parser;
using UnityExplorer.UI;
namespace UnityExplorer.Config
{
public class ExplorerConfig
{
public static ExplorerConfig Instance;
internal static readonly IniDataParser _parser = new IniDataParser();
internal static readonly string INI_PATH = Path.Combine(ExplorerCore.Loader.ConfigFolder, "config.ini");
static ExplorerConfig()
{
_parser.Configuration.CommentString = "#";
PanelDragger.OnFinishResize += PanelDragger_OnFinishResize;
}
// Actual configs
public KeyCode Main_Menu_Toggle = KeyCode.F7;
public bool Force_Unlock_Mouse = true;
public int Default_Page_Limit = 25;
public string Default_Output_Path = Path.Combine(ExplorerCore.EXPLORER_FOLDER, "Output");
public bool Log_Unity_Debug = false;
public bool Hide_On_Startup = false;
public string Window_Anchors = DEFAULT_WINDOW_ANCHORS;
private const string DEFAULT_WINDOW_ANCHORS = "0.25,0.1,0.78,0.95";
public static event Action OnConfigChanged;
internal static void InvokeConfigChanged()
{
OnConfigChanged?.Invoke();
}
public static void OnLoad()
{
Instance = new ExplorerConfig();
if (LoadSettings())
return;
SaveSettings();
}
public static bool LoadSettings()
{
if (!File.Exists(INI_PATH))
return false;
string ini = File.ReadAllText(INI_PATH);
var data = _parser.Parse(ini);
foreach (var config in data.Sections["Config"])
{
switch (config.KeyName)
{
case nameof(Main_Menu_Toggle):
Instance.Main_Menu_Toggle = (KeyCode)Enum.Parse(typeof(KeyCode), config.Value);
break;
case nameof(Force_Unlock_Mouse):
Instance.Force_Unlock_Mouse = bool.Parse(config.Value);
break;
case nameof(Default_Page_Limit):
Instance.Default_Page_Limit = int.Parse(config.Value);
break;
case nameof(Log_Unity_Debug):
Instance.Log_Unity_Debug = bool.Parse(config.Value);
break;
case nameof(Default_Output_Path):
Instance.Default_Output_Path = config.Value;
break;
case nameof(Hide_On_Startup):
Instance.Hide_On_Startup = bool.Parse(config.Value);
break;
case nameof(Window_Anchors):
Instance.Window_Anchors = config.Value;
break;
}
}
return true;
}
public static void SaveSettings()
{
var data = new IniParser.Model.IniData();
data.Sections.AddSection("Config");
var sec = data.Sections["Config"];
sec.AddKey(nameof(Main_Menu_Toggle), Instance.Main_Menu_Toggle.ToString());
sec.AddKey(nameof(Force_Unlock_Mouse), Instance.Force_Unlock_Mouse.ToString());
sec.AddKey(nameof(Default_Page_Limit), Instance.Default_Page_Limit.ToString());
sec.AddKey(nameof(Log_Unity_Debug), Instance.Log_Unity_Debug.ToString());
sec.AddKey(nameof(Default_Output_Path), Instance.Default_Output_Path);
sec.AddKey(nameof(Hide_On_Startup), Instance.Hide_On_Startup.ToString());
sec.AddKey(nameof(Window_Anchors), GetWindowAnchorsString());
if (!Directory.Exists(ExplorerCore.Loader.ConfigFolder))
Directory.CreateDirectory(ExplorerCore.Loader.ConfigFolder);
File.WriteAllText(INI_PATH, data.ToString());
}
// ============ Window Anchors specific stuff ============== //
private static void PanelDragger_OnFinishResize()
{
Instance.Window_Anchors = GetWindowAnchorsString();
SaveSettings();
}
internal Vector4 GetWindowAnchorsVector()
{
try
{
var split = Window_Anchors.Split(',');
Vector4 ret = Vector4.zero;
ret.x = float.Parse(split[0]);
ret.y = float.Parse(split[1]);
ret.z = float.Parse(split[2]);
ret.w = float.Parse(split[3]);
return ret;
}
catch
{
Window_Anchors = DEFAULT_WINDOW_ANCHORS;
return GetWindowAnchorsVector();
}
}
internal static string GetWindowAnchorsString()
{
try
{
var rect = PanelDragger.Instance.Panel;
return $"{rect.anchorMin.x},{rect.anchorMin.y},{rect.anchorMax.x},{rect.anchorMax.y}";
}
catch
{
return DEFAULT_WINDOW_ANCHORS;
}
}
}
}

View File

@ -1,68 +0,0 @@
using System;
using System.IO;
using System.Xml.Serialization;
using UnityEngine;
namespace UnityExplorer.Config
{
public class ModConfig
{
[XmlIgnore] public static readonly XmlSerializer Serializer = new XmlSerializer(typeof(ModConfig));
//[XmlIgnore] private const string EXPLORER_FOLDER = @"Mods\UnityExplorer";
[XmlIgnore] private const string SETTINGS_PATH = ExplorerCore.EXPLORER_FOLDER + @"\config.xml";
[XmlIgnore] public static ModConfig Instance;
// Actual configs
public KeyCode Main_Menu_Toggle = KeyCode.F7;
public bool Force_Unlock_Mouse = true;
public int Default_Page_Limit = 25;
public string Default_Output_Path = ExplorerCore.EXPLORER_FOLDER;
public bool Log_Unity_Debug = false;
public bool Save_Logs_To_Disk = true;
public static event Action OnConfigChanged;
internal static void InvokeConfigChanged()
{
OnConfigChanged?.Invoke();
}
public static void OnLoad()
{
if (LoadSettings())
return;
Instance = new ModConfig();
SaveSettings();
}
public static bool LoadSettings()
{
if (!File.Exists(SETTINGS_PATH))
return false;
try
{
using (var file = File.OpenRead(SETTINGS_PATH))
Instance = (ModConfig)Serializer.Deserialize(file);
}
catch
{
return false;
}
return Instance != null;
}
public static void SaveSettings()
{
if (File.Exists(SETTINGS_PATH))
File.Delete(SETTINGS_PATH);
using (var file = File.Create(SETTINGS_PATH))
Serializer.Serialize(file, Instance);
}
}
}

View File

@ -1,36 +1,37 @@
using System;
using System.IO;
using System.Reflection;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityExplorer.Config;
using UnityExplorer.Helpers;
using UnityExplorer.Input;
using UnityExplorer.Inspectors;
using UnityExplorer.Runtime;
using UnityExplorer.UI;
using UnityExplorer.UI.Modules;
using UnityEngine;
using UnityExplorer.Inspectors;
using System.IO;
using UnityExplorer.Unstrip;
using UnityEngine.SceneManagement;
using UnityExplorer.Helpers;
namespace UnityExplorer
{
public class ExplorerCore
{
public const string NAME = "UnityExplorer";
public const string VERSION = "3.0.3";
public const string VERSION = "3.2.3";
public const string AUTHOR = "Sinai";
public const string GUID = "com.sinai.unityexplorer";
public const string EXPLORER_FOLDER = @"Mods\UnityExplorer";
public static ExplorerCore Instance { get; private set; }
public static bool ShowMenu
{
get => s_showMenu;
set => SetShowMenu(value);
}
public static bool s_showMenu;
public static IExplorerLoader Loader =>
#if ML
ExplorerMelonMod.Instance;
#elif BIE
ExplorerBepInPlugin.Instance;
#elif STANDALONE
ExplorerStandalone.Instance;
#endif
private static bool s_doneUIInit;
private static float s_timeSinceStartup;
public static string EXPLORER_FOLDER => Loader.ExplorerFolder;
public ExplorerCore()
{
@ -42,101 +43,33 @@ namespace UnityExplorer
Instance = this;
#if CPP
ReflectionHelpers.TryLoadGameModules();
#endif
RuntimeProvider.Init();
if (!Directory.Exists(EXPLORER_FOLDER))
Directory.CreateDirectory(EXPLORER_FOLDER);
ModConfig.OnLoad();
ExplorerConfig.OnLoad();
InputManager.Init();
ForceUnlockCursor.Init();
SetupEvents();
ShowMenu = true;
UIManager.ShowMenu = true;
Log($"{NAME} {VERSION} initialized.");
}
public static void Update()
{
if (!s_doneUIInit)
CheckUIInit();
UIManager.CheckUIInit();
if (MouseInspector.Enabled)
MouseInspector.UpdateInspect();
else
{
if (InputManager.GetKeyDown(ModConfig.Instance.Main_Menu_Toggle))
ShowMenu = !ShowMenu;
if (ShowMenu && s_doneUIInit)
UIManager.Update();
}
UIManager.Update();
}
private static void CheckUIInit()
{
s_timeSinceStartup += Time.deltaTime;
if (s_timeSinceStartup > 0.1f)
{
s_doneUIInit = true;
try
{
UIManager.Init();
Log("Initialized UnityExplorer UI.");
}
catch (Exception e)
{
LogWarning($"Exception setting up UI: {e}");
}
}
}
private void SetupEvents()
{
#if CPP
try
{
Application.add_logMessageReceived(new Action<string, string, LogType>(OnUnityLog));
SceneManager.add_sceneLoaded(new Action<Scene, LoadSceneMode>((Scene a, LoadSceneMode b) => { OnSceneLoaded(); }));
SceneManager.add_activeSceneChanged(new Action<Scene, Scene>((Scene a, Scene b) => { OnSceneLoaded(); }));
}
catch { }
#else
Application.logMessageReceived += OnUnityLog;
SceneManager.sceneLoaded += (Scene a, LoadSceneMode b) => { OnSceneLoaded(); };
SceneManager.activeSceneChanged += (Scene a, Scene b) => { OnSceneLoaded(); };
#endif
}
internal void OnSceneLoaded()
{
UIManager.OnSceneChange();
}
private static void SetShowMenu(bool show)
{
s_showMenu = show;
if (UIManager.CanvasRoot)
{
UIManager.CanvasRoot.SetActive(show);
if (show)
ForceUnlockCursor.SetEventSystem();
else
ForceUnlockCursor.ReleaseEventSystem();
}
ForceUnlockCursor.UpdateCursorControl();
}
private void OnUnityLog(string message, string stackTrace, LogType type)
public void OnUnityLog(string message, string stackTrace, LogType type)
{
if (!DebugConsole.LogUnity)
return;
@ -166,11 +99,7 @@ namespace UnityExplorer
if (unity)
return;
#if ML
MelonLoader.MelonLogger.Log(message?.ToString());
#else
ExplorerBepInPlugin.Logging?.LogMessage(message?.ToString());
#endif
Loader.OnLogMessage(message);
}
public static void LogWarning(object message, bool unity = false)
@ -180,11 +109,7 @@ namespace UnityExplorer
if (unity)
return;
#if ML
MelonLoader.MelonLogger.LogWarning(message?.ToString());
#else
ExplorerBepInPlugin.Logging?.LogWarning(message?.ToString());
#endif
Loader.OnLogWarning(message);
}
public static void LogError(object message, bool unity = false)
@ -194,22 +119,7 @@ namespace UnityExplorer
if (unity)
return;
#if ML
MelonLoader.MelonLogger.LogError(message?.ToString());
#else
ExplorerBepInPlugin.Logging?.LogError(message?.ToString());
#endif
}
public static string RemoveInvalidFilenameChars(string s)
{
var invalid = System.IO.Path.GetInvalidFileNameChars();
foreach (var c in invalid)
{
s = s.Replace(c.ToString(), "");
}
return s;
Loader.OnLogError(message);
}
}
}

View File

@ -1,29 +0,0 @@
#if ML
using System;
using MelonLoader;
namespace UnityExplorer
{
public class ExplorerMelonMod : MelonMod
{
public static ExplorerMelonMod Instance;
public override void OnApplicationStart()
{
Instance = this;
new ExplorerCore();
}
public override void OnUpdate()
{
ExplorerCore.Update();
}
public override void OnLevelWasLoaded(int level)
{
ExplorerCore.Instance.OnSceneLoaded();
}
}
}
#endif

View File

@ -1,33 +0,0 @@
#if CPP
using System;
using UnityEngine.Events;
namespace UnityExplorer.Helpers
{
// Possibly temporary, just so Il2Cpp can do the same style "AddListener" as Mono.
// Just saves me having a preprocessor directive for every single AddListener.
public static class EventHelper
{
public static void AddListener(this UnityEvent action, Action listener)
{
action.AddListener(listener);
}
public static void AddListener<T>(this UnityEvent<T> action, Action<T> listener)
{
action.AddListener(listener);
}
public static void AddListener<T0, T1>(this UnityEvent<T0, T1> action, Action<T0, T1> listener)
{
action.AddListener(listener);
}
public static void AddListener<T0, T1, T2>(this UnityEvent<T0, T1, T2> action, Action<T0, T1, T2> listener)
{
action.AddListener(listener);
}
}
}
#endif

View File

@ -11,20 +11,27 @@ namespace UnityExplorer.Helpers
{
private static readonly Dictionary<string, Delegate> iCallCache = new Dictionary<string, Delegate>();
public static T GetICall<T>(string iCallName) where T : Delegate
/// <summary>
/// Helper to get and cache an iCall by providing the signature (eg. "UnityEngine.Resources::FindObjectsOfTypeAll").
/// </summary>
/// <typeparam name="T">The Type of Delegate to provide for the iCall.</typeparam>
/// <param name="signature">The signature of the iCall you want to get.</param>
/// <returns>The <typeparamref name="T"/> delegate if successful.</returns>
/// <exception cref="MissingMethodException">If the iCall could not be found.</exception>
public static T GetICall<T>(string signature) where T : Delegate
{
if (iCallCache.ContainsKey(iCallName))
return (T)iCallCache[iCallName];
if (iCallCache.ContainsKey(signature))
return (T)iCallCache[signature];
IntPtr ptr = il2cpp_resolve_icall(iCallName);
IntPtr ptr = il2cpp_resolve_icall(signature);
if (ptr == IntPtr.Zero)
{
throw new MissingMethodException($"Could not resolve internal call by name '{iCallName}'!");
throw new MissingMethodException($"Could not resolve internal call by name '{signature}'!");
}
Delegate iCall = Marshal.GetDelegateForFunctionPointer(ptr, typeof(T));
iCallCache.Add(iCallName, iCall);
iCallCache.Add(signature, iCall);
return (T)iCall;
}

View File

@ -21,26 +21,61 @@ namespace UnityExplorer.Helpers
{
public static BF CommonFlags = BF.Public | BF.Instance | BF.NonPublic | BF.Static;
// cache for GetTypeByName
internal static readonly Dictionary<string, Type> s_typesByName = new Dictionary<string, Type>();
/// <summary>
/// Find a <see cref="Type"/> in the current AppDomain whose <see cref="Type.FullName"/> matches the provided <paramref name="fullName"/>.
/// </summary>
/// <param name="fullName">The <see cref="Type.FullName"/> you want to search for - case sensitive and full matches only.</param>
/// <returns>The Type if found, otherwise null.</returns>
public static Type GetTypeByName(string fullName)
{
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
s_typesByName.TryGetValue(fullName, out Type ret);
if (ret != null)
return ret;
foreach (var type in from asm in AppDomain.CurrentDomain.GetAssemblies()
from type in asm.TryGetTypes()
select type)
{
foreach (var type in asm.TryGetTypes())
if (type.FullName == fullName)
{
if (type.FullName == fullName)
{
return type;
}
ret = type;
break;
}
}
return null;
if (s_typesByName.ContainsKey(fullName))
s_typesByName[fullName] = ret;
else
s_typesByName.Add(fullName, ret);
return ret;
}
// cache for GetBaseTypes
internal static readonly Dictionary<string, Type[]> s_cachedTypeInheritance = new Dictionary<string, Type[]>();
/// <summary>
/// Get all base types of the provided Type, including itself.
/// </summary>
public static Type[] GetAllBaseTypes(object obj) => GetAllBaseTypes(GetActualType(obj));
/// <summary>
/// Get all base types of the provided Type, including itself.
/// </summary>
public static Type[] GetAllBaseTypes(Type type)
{
if (type == null)
throw new ArgumentNullException("type");
var name = type.AssemblyQualifiedName;
if (s_cachedTypeInheritance.TryGetValue(name, out Type[] ret))
return ret;
List<Type> list = new List<Type>();
while (type != null)
@ -49,19 +84,28 @@ namespace UnityExplorer.Helpers
type = type.BaseType;
}
return list.ToArray();
ret = list.ToArray();
s_cachedTypeInheritance.Add(name, ret);
return ret;
}
public static Type GetActualType(object obj)
/// <summary>
/// Helper for IL2CPP to get the underlying true Type (Unhollowed) of the object.
/// </summary>
/// <param name="obj">The object to get the true Type for.</param>
/// <returns>The most accurate Type of the object which could be identified.</returns>
public static Type GetActualType(this object obj)
{
if (obj == null)
return null;
var type = obj.GetType();
#if CPP
if (obj is Il2CppSystem.Object ilObject)
if (obj is Il2CppSystem.Object cppObject)
{
if (ilObject is CppType)
if (cppObject is CppType)
return typeof(CppType);
if (!string.IsNullOrEmpty(type.Namespace))
@ -69,22 +113,22 @@ namespace UnityExplorer.Helpers
// Il2CppSystem-namespace objects should just return GetType,
// because using GetIl2CppType returns the System namespace type instead.
if (type.Namespace.StartsWith("System.") || type.Namespace.StartsWith("Il2CppSystem."))
return ilObject.GetType();
return cppObject.GetType();
}
var il2cppType = ilObject.GetIl2CppType();
var cppType = cppObject.GetIl2CppType();
// check if type is injected
IntPtr classPtr = il2cpp_object_get_class(ilObject.Pointer);
IntPtr classPtr = il2cpp_object_get_class(cppObject.Pointer);
if (RuntimeSpecificsStore.IsInjected(classPtr))
{
var typeByName = GetTypeByName(il2cppType.FullName);
var typeByName = GetTypeByName(cppType.FullName);
if (typeByName != null)
return typeByName;
}
// this should be fine for all other il2cpp objects
var getType = GetMonoType(il2cppType);
var getType = GetMonoType(cppType);
if (getType != null)
return getType;
}
@ -93,20 +137,69 @@ namespace UnityExplorer.Helpers
}
#if CPP
private static readonly Dictionary<CppType, Type> Il2CppToMonoType = new Dictionary<CppType, Type>();
// caching for GetMonoType
private static readonly Dictionary<string, Type> Il2CppToMonoType = new Dictionary<string, Type>();
// keep unobfuscated type name cache, used to display proper name.
internal static Dictionary<string, string> UnobfuscatedTypeNames = new Dictionary<string, string>();
/// <summary>
/// Try to get the Mono (Unhollowed) Type representation of the provided <see cref="Il2CppSystem.Type"/>.
/// </summary>
/// <param name="cppType">The Cpp Type you want to convert to Mono.</param>
/// <returns>The Mono Type if found, otherwise null.</returns>
public static Type GetMonoType(CppType cppType)
{
if (Il2CppToMonoType.ContainsKey(cppType))
return Il2CppToMonoType[cppType];
string name = cppType.AssemblyQualifiedName;
var getType = Type.GetType(cppType.AssemblyQualifiedName);
Il2CppToMonoType.Add(cppType, getType);
return getType;
if (Il2CppToMonoType.ContainsKey(name))
return Il2CppToMonoType[name];
Type ret = Type.GetType(name);
if (ret == null)
{
string baseName = cppType.FullName;
string baseAssembly = cppType.Assembly.GetName().name;
ret = AppDomain.CurrentDomain
.GetAssemblies()
.FirstOrDefault(a
=> a.GetName().Name == baseAssembly)?
.TryGetTypes()
.FirstOrDefault(t
=> t.CustomAttributes.Any(ca
=> ca.AttributeType.Name == "ObfuscatedNameAttribute"
&& (string)ca.ConstructorArguments[0].Value == baseName));
if (ret != null)
{
// unobfuscated type was found, add to cache.
UnobfuscatedTypeNames.Add(cppType.FullName, ret.FullName);
}
}
Il2CppToMonoType.Add(name, ret);
return ret;
}
private static readonly Dictionary<Type, IntPtr> CppClassPointers = new Dictionary<Type, IntPtr>();
// cached class pointers for Il2CppCast
private static readonly Dictionary<string, IntPtr> CppClassPointers = new Dictionary<string, IntPtr>();
/// <summary>
/// Attempt to cast the object to its underlying type.
/// </summary>
/// <param name="obj">The object you want to cast.</param>
/// <returns>The object, as the underlying type if successful or the input value if not.</returns>
public static object Il2CppCast(this object obj) => Il2CppCast(obj, GetActualType(obj));
/// <summary>
/// Attempt to cast the object to the provided type.
/// </summary>
/// <param name="obj">The object you want to cast.</param>
/// <param name="castTo">The Type you want to cast to.</param>
/// <returns>The object, as the type (or a normal C# object) if successful or the input value if not.</returns>
public static object Il2CppCast(this object obj, Type castTo)
{
if (!(obj is Il2CppSystem.Object ilObj))
@ -126,31 +219,77 @@ namespace UnityExplorer.Helpers
return Activator.CreateInstance(castTo, ilObj.Pointer);
}
/// <summary>
/// Get the Il2Cpp Class Pointer for the provided Mono (Unhollowed) Type.
/// </summary>
/// <param name="type">The Mono/Unhollowed Type you want the Il2Cpp Class Pointer for.</param>
/// <returns>True if successful, false if not.</returns>
public static bool Il2CppTypeNotNull(Type type) => Il2CppTypeNotNull(type, out _);
/// <summary>
/// Get the Il2Cpp Class Pointer for the provided Mono (Unhollowed) Type.
/// </summary>
/// <param name="type">The Mono/Unhollowed Type you want the Il2Cpp Class Pointer for.</param>
/// <param name="il2cppPtr">The IntPtr for the Il2Cpp class, or IntPtr.Zero if not found.</param>
/// <returns>True if successful, false if not.</returns>
public static bool Il2CppTypeNotNull(Type type, out IntPtr il2cppPtr)
{
if (!CppClassPointers.ContainsKey(type))
{
il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
if (CppClassPointers.TryGetValue(type.AssemblyQualifiedName, out il2cppPtr))
return il2cppPtr != IntPtr.Zero;
il2cppPtr = (IntPtr)typeof(Il2CppClassPointerStore<>)
.MakeGenericType(new Type[] { type })
.GetField("NativeClassPtr", BF.Public | BF.Static)
.GetValue(null);
CppClassPointers.Add(type, il2cppPtr);
}
else
il2cppPtr = CppClassPointers[type];
CppClassPointers.Add(type.AssemblyQualifiedName, il2cppPtr);
return il2cppPtr != IntPtr.Zero;
}
// Extern C++ methods
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern bool il2cpp_class_is_assignable_from(IntPtr klass, IntPtr oklass);
[DllImport("GameAssembly", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
public static extern IntPtr il2cpp_object_get_class(IntPtr obj);
// cached il2cpp unbox methods
internal static readonly Dictionary<string, MethodInfo> s_unboxMethods = new Dictionary<string, MethodInfo>();
/// <summary>
/// Attempt to unbox the object to the underlying struct type.
/// </summary>
/// <param name="obj">The object which is a struct underneath.</param>
/// <returns>The struct if successful, otherwise null.</returns>
public static object Unbox(this object obj) => Unbox(obj, GetActualType(obj));
/// <summary>
/// Attempt to unbox the object to the struct type.
/// </summary>
/// <param name="obj">The object which is a struct underneath.</param>
/// <param name="type">The type of the struct you want to unbox to.</param>
/// <returns>The struct if successful, otherwise null.</returns>
public static object Unbox(this object obj, Type type)
{
if (!type.IsValueType)
return null;
if (!(obj is Il2CppSystem.Object))
return obj;
var name = type.AssemblyQualifiedName;
if (!s_unboxMethods.ContainsKey(name))
{
s_unboxMethods.Add(name, typeof(Il2CppObjectBase)
.GetMethod("Unbox")
.MakeGenericMethod(type));
}
return s_unboxMethods[name].Invoke(obj, new object[0]);
}
#endif
public static IEnumerable<Type> TryGetTypes(this Assembly asm)
@ -176,6 +315,74 @@ namespace UnityExplorer.Helpers
}
}
// Helper for IL2CPP to check if a Type is assignable to IEnumerable
#if CPP
internal static IntPtr s_cppEnumerableClassPtr;
#endif
/// <summary>
/// Check if the provided Type is assignable to IEnumerable.
/// </summary>
/// <param name="t">The Type to check</param>
/// <returns>True if the Type is assignable to IEnumerable, otherwise false.</returns>
public static bool IsEnumerable(Type t)
{
if (typeof(IEnumerable).IsAssignableFrom(t))
return true;
#if CPP
try
{
if (s_cppEnumerableClassPtr == IntPtr.Zero)
Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IEnumerable), out s_cppEnumerableClassPtr);
if (s_cppEnumerableClassPtr != IntPtr.Zero
&& Il2CppTypeNotNull(t, out IntPtr classPtr)
&& il2cpp_class_is_assignable_from(s_cppEnumerableClassPtr, classPtr))
{
return true;
}
}
catch { }
#endif
return false;
}
// Helper for IL2CPP to check if a Type is assignable to IDictionary
#if CPP
internal static IntPtr s_cppDictionaryClassPtr;
#endif
/// <summary>
/// Check if the provided Type is assignable to IDictionary.
/// </summary>
/// <param name="t">The Type to check</param>
/// <returns>True if the Type is assignable to IDictionary, otherwise false.</returns>
public static bool IsDictionary(Type t)
{
if (typeof(IDictionary).IsAssignableFrom(t))
return true;
#if CPP
try
{
if (s_cppDictionaryClassPtr == IntPtr.Zero)
if (!Il2CppTypeNotNull(typeof(Il2CppSystem.Collections.IDictionary), out s_cppDictionaryClassPtr))
return false;
if (Il2CppTypeNotNull(t, out IntPtr classPtr))
{
if (il2cpp_class_is_assignable_from(s_cppDictionaryClassPtr, classPtr))
return true;
}
}
catch { }
#endif
return false;
}
// Helper for IL2CPP to try to make sure the Unhollowed game assemblies are actually loaded.
#if CPP
internal static void TryLoadGameModules()
{
@ -186,11 +393,10 @@ namespace UnityExplorer.Helpers
public static bool LoadModule(string module)
{
#if ML
var path = $@"MelonLoader\Managed\{module}.dll";
var path = Path.Combine("MelonLoader", "Managed", $"{module}.dll");
#else
var path = $@"BepInEx\unhollowed\{module}.dll";
var path = Path.Combine("BepInEx", "unhollowed", $"{module}.dll");
#endif
return LoadModuleInternal(path);
}
@ -212,67 +418,31 @@ namespace UnityExplorer.Helpers
return false;
}
#else
// For Mono, just return true and do nothing, Mono will sort it out itself.
public static bool LoadModule(string module) => true;
#endif
public static bool IsEnumerable(Type t)
{
if (typeof(IEnumerable).IsAssignableFrom(t))
{
return true;
}
#if CPP
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
{
return typeof(Il2CppSystem.Collections.Generic.List<>).IsAssignableFrom(g)
|| typeof(Il2CppSystem.Collections.Generic.IList<>).IsAssignableFrom(g)
|| typeof(Il2CppSystem.Collections.Generic.HashSet<>).IsAssignableFrom(g);
}
else
{
return typeof(Il2CppSystem.Collections.IList).IsAssignableFrom(t);
}
#else
return false;
#endif
}
public static bool IsDictionary(Type t)
{
if (typeof(IDictionary).IsAssignableFrom(t))
{
return true;
}
#if CPP
if (t.IsGenericType && t.GetGenericTypeDefinition() is Type g)
{
return typeof(Il2CppSystem.Collections.Generic.Dictionary<,>).IsAssignableFrom(g)
|| typeof(Il2CppSystem.Collections.Generic.IDictionary<,>).IsAssignableFrom(g);
}
else
{
return typeof(Il2CppSystem.Collections.IDictionary).IsAssignableFrom(t)
|| typeof(Il2CppSystem.Collections.Hashtable).IsAssignableFrom(t);
}
#else
return false;
#endif
}
/// <summary>
/// Helper to display a simple "{ExceptionType}: {Message}" of the exception, and optionally use the inner-most exception.
/// </summary>
/// <param name="e">The Exception to convert to string.</param>
/// <param name="innerMost">Should the inner-most Exception of the stack be used? If false, the Exception you provided will be used directly.</param>
/// <returns>The exception to string.</returns>
public static string ExceptionToString(Exception e, bool innerMost = false)
{
while (innerMost && e.InnerException != null)
if (innerMost)
{
while (e.InnerException != null)
{
#if CPP
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException runtimeEx)
break;
if (e.InnerException is System.Runtime.CompilerServices.RuntimeWrappedException)
break;
#endif
e = e.InnerException;
e = e.InnerException;
}
}
return e.GetType() + ", " + e.Message;
return $"{e.GetType()}: {e.Message}";
}
}
}

View File

@ -10,12 +10,21 @@ namespace UnityExplorer.Helpers
{
public static class Texture2DHelpers
{
#if CPP // If Mono
#else
#if MONO
private static bool isNewEncodeMethod = false;
private static MethodInfo EncodeToPNGMethod => m_encodeToPNGMethod ?? GetEncodeToPNGMethod();
private static MethodInfo m_encodeToPNGMethod;
public static byte[] EncodeToPNGSafe(this Texture2D tex)
{
var method = EncodeToPNGMethod;
if (method.IsStatic)
return (byte[])method.Invoke(null, new object[] { tex });
else
return (byte[])method.Invoke(tex, new object[0]);
}
private static MethodInfo GetEncodeToPNGMethod()
{
if (ReflectionHelpers.GetTypeByName("UnityEngine.ImageConversion") is Type imageConversion)
@ -35,7 +44,6 @@ namespace UnityExplorer.Helpers
}
#endif
public static bool IsReadable(this Texture2D tex)
{
try
@ -53,7 +61,7 @@ namespace UnityExplorer.Helpers
}
}
public static Texture2D Copy(Texture2D orig, Rect rect) //, bool isDTXnmNormal = false)
public static Texture2D Copy(Texture2D orig, Rect rect)
{
Color[] pixels;
@ -62,12 +70,21 @@ namespace UnityExplorer.Helpers
pixels = orig.GetPixels((int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
// use full constructor for better compatibility
#if CPP
var _newTex = new Texture2D((int)rect.width, (int)rect.height, TextureFormat.RGBA32, Texture.GenerateAllMips, false, IntPtr.Zero);
#else
var _newTex = new Texture2D((int)rect.width, (int)rect.height);
#endif
_newTex.SetPixels(pixels);
return _newTex;
}
#if CPP
internal delegate void d_Blit2(IntPtr source, IntPtr dest);
#endif
public static Texture2D ForceReadTexture(Texture2D tex)
{
try
@ -78,7 +95,13 @@ namespace UnityExplorer.Helpers
var rt = RenderTexture.GetTemporary(tex.width, tex.height, 0, RenderTextureFormat.ARGB32);
rt.filterMode = FilterMode.Point;
RenderTexture.active = rt;
#if MONO
Graphics.Blit(tex, rt);
#else
var iCall = ICallHelper.GetICall<d_Blit2>("UnityEngine.Graphics::Blit2");
iCall.Invoke(tex.Pointer, rt.Pointer);
#endif
var _newTex = new Texture2D(tex.width, tex.height, TextureFormat.ARGB32, false);

View File

@ -5,6 +5,7 @@
<ItemGroup>
<InputAssemblies Include="$(OutputPath)$(AssemblyName).dll" />
<InputAssemblies Include="..\lib\mcs.dll" />
<InputAssemblies Include="..\lib\INIFileParser.dll" />
</ItemGroup>
<ILRepack

View File

@ -80,7 +80,11 @@ namespace UnityExplorer.Inspectors.GameObjects
text.text = UISyntaxHighlight.ParseFullSyntax(ReflectionHelpers.GetActualType(comp), true);
var toggle = s_compToggles[i];
#if CPP
if (comp.TryCast<Behaviour>() is Behaviour behaviour)
#else
if (comp is Behaviour behaviour)
#endif
{
if (!toggle.gameObject.activeSelf)
toggle.gameObject.SetActive(true);
@ -107,8 +111,11 @@ namespace UnityExplorer.Inspectors.GameObjects
internal static void OnCompToggleClicked(int index, bool value)
{
var comp = s_compShortlist[index];
#if CPP
comp.TryCast<Behaviour>().enabled = value;
#else
(comp as Behaviour).enabled = value;
#endif
}
internal static void OnCompListObjectClicked(int index)
@ -130,7 +137,7 @@ namespace UnityExplorer.Inspectors.GameObjects
}
#region UI CONSTRUCTION
#region UI CONSTRUCTION
internal void ConstructCompList(GameObject parent)
{
@ -168,34 +175,34 @@ namespace UnityExplorer.Inspectors.GameObjects
{
int thisIndex = s_compListTexts.Count;
GameObject btnGroupObj = UIFactory.CreateHorizontalGroup(s_compListContent, new Color(0.07f, 0.07f, 0.07f));
HorizontalLayoutGroup btnGroup = btnGroupObj.GetComponent<HorizontalLayoutGroup>();
btnGroup.childForceExpandWidth = true;
btnGroup.childControlWidth = true;
btnGroup.childForceExpandHeight = false;
btnGroup.childControlHeight = true;
btnGroup.childAlignment = TextAnchor.MiddleLeft;
LayoutElement btnLayout = btnGroupObj.AddComponent<LayoutElement>();
btnLayout.minWidth = 25;
btnLayout.flexibleWidth = 999;
btnLayout.minHeight = 25;
btnLayout.flexibleHeight = 0;
btnGroupObj.AddComponent<Mask>();
GameObject groupObj = UIFactory.CreateHorizontalGroup(s_compListContent, new Color(0.07f, 0.07f, 0.07f));
HorizontalLayoutGroup group = groupObj.GetComponent<HorizontalLayoutGroup>();
group.childForceExpandWidth = true;
group.childControlWidth = true;
group.childForceExpandHeight = false;
group.childControlHeight = true;
group.childAlignment = TextAnchor.MiddleLeft;
LayoutElement groupLayout = groupObj.AddComponent<LayoutElement>();
groupLayout.minWidth = 25;
groupLayout.flexibleWidth = 999;
groupLayout.minHeight = 25;
groupLayout.flexibleHeight = 0;
groupObj.AddComponent<Mask>();
// Behaviour enabled toggle
var toggleObj = UIFactory.CreateToggle(btnGroupObj, out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f));
var toggleObj = UIFactory.CreateToggle(groupObj, out Toggle toggle, out Text toggleText, new Color(0.3f, 0.3f, 0.3f));
var toggleLayout = toggleObj.AddComponent<LayoutElement>();
toggleLayout.minHeight = 25;
toggleLayout.minWidth = 25;
toggleText.text = "";
toggle.isOn = false;
toggle.isOn = true;
s_compToggles.Add(toggle);
toggle.onValueChanged.AddListener((bool val) => { OnCompToggleClicked(thisIndex, val); });
// Main component button
GameObject mainButtonObj = UIFactory.CreateButton(btnGroupObj);
GameObject mainButtonObj = UIFactory.CreateButton(groupObj);
LayoutElement mainBtnLayout = mainButtonObj.AddComponent<LayoutElement>();
mainBtnLayout.minHeight = 25;
mainBtnLayout.flexibleHeight = 0;
@ -224,6 +231,6 @@ namespace UnityExplorer.Inspectors.GameObjects
}
#endregion
#endregion
}
}

View File

@ -38,7 +38,7 @@ namespace UnityExplorer.Inspectors
}
}
public void Inspect(object obj)
public void Inspect(object obj, CacheObjectBase parentMember = null)
{
#if CPP
obj = obj.Il2CppCast(ReflectionHelpers.GetActualType(obj));
@ -76,6 +76,9 @@ namespace UnityExplorer.Inspectors
else
inspector = new InstanceInspector(obj);
if (inspector is ReflectionInspector ri)
ri.ParentMember = parentMember;
m_currentInspectors.Add(inspector);
SetInspectorTab(inspector);
}
@ -231,23 +234,34 @@ namespace UnityExplorer.Inspectors
invisGroup.spacing = 10;
// inspect under mouse button
AddMouseInspectButton(topRowObj, MouseInspector.MouseInspectMode.UI);
AddMouseInspectButton(topRowObj, MouseInspector.MouseInspectMode.World);
}
private static void AddMouseInspectButton(GameObject topRowObj, MouseInspector.MouseInspectMode mode)
{
var inspectObj = UIFactory.CreateButton(topRowObj);
var inspectLayout = inspectObj.AddComponent<LayoutElement>();
inspectLayout.minWidth = 120;
inspectLayout.flexibleWidth = 0;
var inspectText = inspectObj.GetComponentInChildren<Text>();
inspectText.text = "Mouse Inspect";
inspectText.fontSize = 13;
if (mode == MouseInspector.MouseInspectMode.UI)
inspectText.text += " (UI)";
var inspectBtn = inspectObj.GetComponent<Button>();
var inspectColors = inspectBtn.colors;
inspectColors.normalColor = new Color(0.2f, 0.2f, 0.2f);
inspectBtn.colors = inspectColors;
var inspectText = inspectObj.GetComponentInChildren<Text>();
inspectText.text = "Mouse Inspect";
inspectText.fontSize = 13;
inspectBtn.onClick.AddListener(OnInspectMouseClicked);
void OnInspectMouseClicked()
{
MouseInspector.Mode = mode;
MouseInspector.StartInspect();
}
}

View File

@ -3,18 +3,27 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.UI;
using UnityExplorer.Helpers;
using UnityExplorer.Input;
using UnityExplorer.UI;
using UnityExplorer.Unstrip;
namespace UnityExplorer.Inspectors
{
public class MouseInspector
{
public enum MouseInspectMode
{
World,
UI
}
public static bool Enabled { get; set; }
//internal static Text s_objUnderMouseName;
public static MouseInspectMode Mode { get; set; }
internal static Text s_objNameLabel;
internal static Text s_objPathLabel;
internal static Text s_mousePosLabel;
@ -29,6 +38,18 @@ namespace UnityExplorer.Inspectors
Enabled = true;
MainMenu.Instance.MainPanel.SetActive(false);
s_UIContent.SetActive(true);
// recache Graphic Raycasters each time we start
var casters = ResourcesUnstrip.FindObjectsOfTypeAll(typeof(GraphicRaycaster));
m_gCasters = new GraphicRaycaster[casters.Length];
for (int i = 0; i < casters.Length; i++)
{
#if CPP
m_gCasters[i] = casters[i].TryCast<GraphicRaycaster>();
#else
m_gCasters[i] = casters[i] as GraphicRaycaster;
#endif
}
}
public static void StopInspect()
@ -40,50 +61,60 @@ namespace UnityExplorer.Inspectors
ClearHitData();
}
internal static GraphicRaycaster[] m_gCasters;
public static void UpdateInspect()
{
if (InputManager.GetKeyDown(KeyCode.Escape))
{
StopInspect();
return;
}
var mousePos = InputManager.MousePosition;
if (mousePos != s_lastMousePos)
{
s_lastMousePos = mousePos;
var inversePos = UIManager.CanvasRoot.transform.InverseTransformPoint(mousePos);
s_mousePosLabel.text = $"<color=grey>Mouse Position:</color> {((Vector2)InputManager.MousePosition).ToString()}";
float yFix = mousePos.y < 120 ? 80 : -80;
s_UIContent.transform.localPosition = new Vector3(inversePos.x, inversePos.y + yFix, 0);
}
UpdatePosition(mousePos);
if (!UnityHelpers.MainCamera)
return;
// actual inspect raycast
var ray = UnityHelpers.MainCamera.ScreenPointToRay(mousePos);
if (Physics.Raycast(ray, out RaycastHit hit, 1000f))
switch (Mode)
{
case MouseInspectMode.UI:
RaycastUI(mousePos); break;
case MouseInspectMode.World:
RaycastWorld(mousePos); break;
}
}
internal static void OnHitGameObject(GameObject obj)
{
if (obj != s_lastHit)
{
s_lastHit = obj;
s_objNameLabel.text = $"<b>Click to Inspect:</b> <color=cyan>{obj.name}</color>";
s_objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}";
}
if (InputManager.GetMouseButtonDown(0))
{
StopInspect();
InspectorManager.Instance.Inspect(obj);
}
}
internal static void RaycastWorld(Vector2 mousePos)
{
var ray = UnityHelpers.MainCamera.ScreenPointToRay(mousePos);
Physics.Raycast(ray, out RaycastHit hit, 1000f);
if (hit.transform)
{
var obj = hit.transform.gameObject;
if (obj != s_lastHit)
{
s_lastHit = obj;
s_objNameLabel.text = $"<b>Click to Inspect:</b> <color=cyan>{obj.name}</color>";
s_objPathLabel.text = $"Path: {obj.transform.GetTransformPath(true)}";
}
if (InputManager.GetMouseButtonDown(0))
{
StopInspect();
InspectorManager.Instance.Inspect(obj);
}
OnHitGameObject(obj);
}
else
{
@ -92,6 +123,56 @@ namespace UnityExplorer.Inspectors
}
}
internal static void RaycastUI(Vector2 mousePos)
{
var ped = new PointerEventData(null)
{
position = mousePos
};
#if MONO
var list = new List<RaycastResult>();
#else
var list = new Il2CppSystem.Collections.Generic.List<RaycastResult>();
#endif
foreach (var gr in m_gCasters)
{
gr.Raycast(ped, list);
if (list.Count > 0)
{
foreach (var hit in list)
{
if (hit.gameObject)
{
var obj = hit.gameObject;
OnHitGameObject(obj);
break;
}
}
}
else
{
if (s_lastHit)
ClearHitData();
}
}
}
internal static void UpdatePosition(Vector2 mousePos)
{
s_lastMousePos = mousePos;
var inversePos = UIManager.CanvasRoot.transform.InverseTransformPoint(mousePos);
s_mousePosLabel.text = $"<color=grey>Mouse Position:</color> {mousePos.ToString()}";
float yFix = mousePos.y < 120 ? 80 : -80;
s_UIContent.transform.localPosition = new Vector3(inversePos.x, inversePos.y + yFix, 0);
}
internal static void ClearHitData()
{
s_lastHit = null;
@ -99,7 +180,7 @@ namespace UnityExplorer.Inspectors
s_objPathLabel.text = "";
}
#region UI Construction
#region UI Construction
internal static void ConstructUI()
{
@ -112,7 +193,10 @@ namespace UnityExplorer.Inspectors
baseRect.anchorMin = half;
baseRect.anchorMax = half;
baseRect.pivot = half;
baseRect.sizeDelta = new Vector2(700, 100);
baseRect.sizeDelta = new Vector2(700, 150);
var group = content.GetComponent<VerticalLayoutGroup>();
group.childForceExpandHeight = true;
// Title text
@ -131,13 +215,16 @@ namespace UnityExplorer.Inspectors
var pathLabelObj = UIFactory.CreateLabel(content, TextAnchor.MiddleLeft);
s_objPathLabel = pathLabelObj.GetComponent<Text>();
s_objPathLabel.color = Color.grey;
s_objPathLabel.fontStyle = FontStyle.Italic;
s_objPathLabel.horizontalOverflow = HorizontalWrapMode.Overflow;
s_objPathLabel.horizontalOverflow = HorizontalWrapMode.Wrap;
var pathLayout = pathLabelObj.AddComponent<LayoutElement>();
pathLayout.minHeight = 75;
pathLayout.flexibleHeight = 0;
s_UIContent.SetActive(false);
}
#endregion
#endregion
}
}

View File

@ -33,6 +33,9 @@ namespace UnityExplorer.Inspectors.Reflection
{
var fi = MemInfo as FieldInfo;
fi.SetValue(fi.IsStatic ? null : DeclaringInstance, IValue.Value);
if (this.ParentInspector?.ParentMember != null)
this.ParentInspector.ParentMember.SetValue();
}
}
}

View File

@ -19,6 +19,7 @@ namespace UnityExplorer.Inspectors.Reflection
public override Type FallbackType { get; }
public ReflectionInspector ParentInspector { get; set; }
public MemberInfo MemInfo { get; set; }
public Type DeclaringType { get; set; }
public object DeclaringInstance { get; set; }
@ -380,7 +381,7 @@ namespace UnityExplorer.Inspectors.Reflection
argLabelLayout.minHeight = 25;
var argText = argLabelObj.GetComponent<Text>();
var argTypeTxt = UISyntaxHighlight.ParseFullSyntax(arg.ParameterType, false);
argText.text = $"{argTypeTxt} <color={UISyntaxHighlight.Local}>{arg.Name}</color>";
argText.text = $"{argTypeTxt} <color={UISyntaxHighlight.LOCAL_ARG}>{arg.Name}</color>";
var argInputObj = UIFactory.CreateInputField(rowObj, 14, (int)TextAnchor.MiddleLeft, 1);
var argInputLayout = argInputObj.AddComponent<LayoutElement>();

View File

@ -71,6 +71,9 @@ namespace UnityExplorer.Inspectors.Reflection
}
catch (Exception e)
{
while (e.InnerException != null)
e = e.InnerException;
ExplorerCore.LogWarning($"Exception evaluating: {e.GetType()}, {e.Message}");
ReflectionException = ReflectionHelpers.ExceptionToString(e);
}
@ -165,7 +168,7 @@ namespace UnityExplorer.Inspectors.Reflection
//var argLayout = argLabelObj.AddComponent<LayoutElement>();
//argLayout.minWidth = 20;
var argText = argLabelObj.GetComponent<Text>();
argText.text = $"{constrainTxt} <color={UISyntaxHighlight.Enum}>{arg.Name}</color>";
argText.text = $"{constrainTxt} <color={UISyntaxHighlight.CONST_VAR}>{arg.Name}</color>";
var argInputObj = UIFactory.CreateInputField(rowObj, 14, (int)TextAnchor.MiddleLeft, 1);
var argInputLayout = argInputObj.AddComponent<LayoutElement>();

View File

@ -63,6 +63,9 @@ namespace UnityExplorer.Inspectors.Reflection
var target = pi.GetAccessors()[0].IsStatic ? null : DeclaringInstance;
pi.SetValue(target, IValue.Value, ParseArguments());
if (this.ParentInspector?.ParentMember != null)
this.ParentInspector.ParentMember.SetValue();
}
}
}

View File

@ -4,6 +4,8 @@ using UnityEngine;
using UnityExplorer.Helpers;
using UnityExplorer.UI;
using UnityEngine.UI;
using UnityExplorer.Unstrip;
using System.IO;
namespace UnityExplorer.Inspectors.Reflection
{
@ -70,8 +72,8 @@ namespace UnityExplorer.Inspectors.Reflection
// WIP
//if (m_targetType == typeof(Texture2D))
// ConstructTextureHelper();
if (m_targetType == typeof(Texture2D))
ConstructTextureHelper();
}
internal void ConstructCompHelper(GameObject rowObj)
@ -136,94 +138,176 @@ namespace UnityExplorer.Inspectors.Reflection
//btn.onClick.AddListener(() => { InspectorManager.Instance.Inspect(comp.gameObject); });
}
//internal bool showingTextureHelper;
//internal bool constructedTextureViewer;
internal bool showingTextureHelper;
internal bool constructedTextureViewer;
//internal void ConstructTextureHelper()
//{
// var rowObj = UIFactory.CreateHorizontalGroup(Content, new Color(0.1f, 0.1f, 0.1f));
// var rowLayout = rowObj.AddComponent<LayoutElement>();
// rowLayout.minHeight = 25;
// rowLayout.flexibleHeight = 0;
// var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
// rowGroup.childForceExpandHeight = true;
// rowGroup.childForceExpandWidth = false;
// rowGroup.padding.top = 3;
// rowGroup.padding.left = 3;
// rowGroup.padding.bottom = 3;
// rowGroup.padding.right = 3;
// rowGroup.spacing = 5;
internal GameObject m_textureViewerObj;
// var showBtnObj = UIFactory.CreateButton(rowObj, new Color(0.2f, 0.2f, 0.2f));
// var showBtnLayout = showBtnObj.AddComponent<LayoutElement>();
// showBtnLayout.minWidth = 50;
// showBtnLayout.flexibleWidth = 0;
// var showText = showBtnObj.GetComponentInChildren<Text>();
// showText.text = "Show";
// var showBtn = showBtnObj.GetComponent<Button>();
internal void ConstructTextureHelper()
{
var rowObj = UIFactory.CreateHorizontalGroup(Content, new Color(0.1f, 0.1f, 0.1f));
var rowLayout = rowObj.AddComponent<LayoutElement>();
rowLayout.minHeight = 25;
rowLayout.flexibleHeight = 0;
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
rowGroup.childForceExpandHeight = true;
rowGroup.childForceExpandWidth = false;
rowGroup.padding.top = 3;
rowGroup.padding.left = 3;
rowGroup.padding.bottom = 3;
rowGroup.padding.right = 3;
rowGroup.spacing = 5;
// var labelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
// var labelText = labelObj.GetComponent<Text>();
// labelText.text = "Texture Viewer";
var showBtnObj = UIFactory.CreateButton(rowObj, new Color(0.2f, 0.6f, 0.2f));
var showBtnLayout = showBtnObj.AddComponent<LayoutElement>();
showBtnLayout.minWidth = 50;
showBtnLayout.flexibleWidth = 0;
var showText = showBtnObj.GetComponentInChildren<Text>();
showText.text = "Show";
var showBtn = showBtnObj.GetComponent<Button>();
// var textureViewerObj = UIFactory.CreateScrollView(Content, out GameObject scrollContent, out _, new Color(0.1f, 0.1f, 0.1f));
// var viewerGroup = scrollContent.GetComponent<VerticalLayoutGroup>();
// viewerGroup.childForceExpandHeight = false;
// viewerGroup.childForceExpandWidth = false;
// viewerGroup.childControlHeight = true;
// viewerGroup.childControlWidth = true;
// var mainLayout = textureViewerObj.GetComponent<LayoutElement>();
// mainLayout.flexibleHeight = -1;
// mainLayout.flexibleWidth = 2000;
// mainLayout.minHeight = 25;
var labelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
var labelText = labelObj.GetComponent<Text>();
labelText.text = "Texture Viewer";
// textureViewerObj.SetActive(false);
var textureViewerObj = UIFactory.CreateScrollView(Content, out GameObject scrollContent, out _, new Color(0.1f, 0.1f, 0.1f));
var viewerGroup = scrollContent.GetComponent<VerticalLayoutGroup>();
viewerGroup.childForceExpandHeight = false;
viewerGroup.childForceExpandWidth = false;
viewerGroup.childControlHeight = true;
viewerGroup.childControlWidth = true;
var mainLayout = textureViewerObj.GetComponent<LayoutElement>();
mainLayout.flexibleHeight = 9999;
mainLayout.flexibleWidth = 9999;
mainLayout.minHeight = 100;
// showBtn.onClick.AddListener(() =>
// {
// showingTextureHelper = !showingTextureHelper;
textureViewerObj.SetActive(false);
// if (showingTextureHelper)
// {
// if (!constructedTextureViewer)
// ConstructTextureViewerArea(scrollContent);
m_textureViewerObj = textureViewerObj;
// showText.text = "Hide";
// textureViewerObj.SetActive(true);
// }
// else
// {
// showText.text = "Show";
// textureViewerObj.SetActive(false);
// }
// });
//}
showBtn.onClick.AddListener(() =>
{
showingTextureHelper = !showingTextureHelper;
//internal void ConstructTextureViewerArea(GameObject parent)
//{
// constructedTextureViewer = true;
if (showingTextureHelper)
{
if (!constructedTextureViewer)
ConstructTextureViewerArea(scrollContent);
// var tex = Target as Texture2D;
showText.text = "Hide";
ToggleTextureViewer(true);
}
else
{
showText.text = "Show";
ToggleTextureViewer(false);
}
});
}
// if (!tex)
// {
// ExplorerCore.LogWarning("Could not cast the target instance to Texture2D!");
// return;
// }
internal void ConstructTextureViewerArea(GameObject parent)
{
constructedTextureViewer = true;
// var imageObj = UIFactory.CreateUIObject("TextureViewerImage", parent, new Vector2(1, 1));
// var image = imageObj.AddComponent<Image>();
// var sprite = UIManager.CreateSprite(tex);
// image.sprite = sprite;
var tex = Target as Texture2D;
#if CPP
if (!tex)
tex = (Target as Il2CppSystem.Object).TryCast<Texture2D>();
#endif
// var fitter = imageObj.AddComponent<ContentSizeFitter>();
// fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
// //fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
if (!tex)
{
ExplorerCore.LogWarning("Could not cast the target instance to Texture2D! Maybe its null or destroyed?");
return;
}
// var imageLayout = imageObj.AddComponent<LayoutElement>();
// imageLayout.preferredHeight = sprite.rect.height;
// imageLayout.preferredWidth = sprite.rect.width;
//}
// Save helper
var saveRowObj = UIFactory.CreateHorizontalGroup(parent, new Color(0.1f, 0.1f, 0.1f));
var saveRow = saveRowObj.GetComponent<HorizontalLayoutGroup>();
saveRow.childForceExpandHeight = true;
saveRow.childForceExpandWidth = true;
saveRow.padding = new RectOffset() { left = 2, bottom = 2, right = 2, top = 2 };
saveRow.spacing = 2;
var btnObj = UIFactory.CreateButton(saveRowObj, new Color(0.2f, 0.2f, 0.2f));
var btnLayout = btnObj.AddComponent<LayoutElement>();
btnLayout.minHeight = 25;
btnLayout.minWidth = 100;
btnLayout.flexibleWidth = 0;
var saveBtn = btnObj.GetComponent<Button>();
var saveBtnText = btnObj.GetComponentInChildren<Text>();
saveBtnText.text = "Save .PNG";
var inputObj = UIFactory.CreateInputField(saveRowObj);
var inputLayout = inputObj.AddComponent<LayoutElement>();
inputLayout.minHeight = 25;
inputLayout.minWidth = 100;
inputLayout.flexibleWidth = 9999;
var inputField = inputObj.GetComponent<InputField>();
var name = tex.name;
if (string.IsNullOrEmpty(name))
name = "untitled";
var savePath = $@"{Config.ExplorerConfig.Instance.Default_Output_Path}\{name}.png";
inputField.text = savePath;
saveBtn.onClick.AddListener(() =>
{
if (tex && !string.IsNullOrEmpty(inputField.text))
{
var path = inputField.text;
if (!path.EndsWith(".png", StringComparison.InvariantCultureIgnoreCase))
{
ExplorerCore.LogWarning("Desired save path must end with '.png'!");
return;
}
var dir = Path.GetDirectoryName(path);
if (!Directory.Exists(dir))
Directory.CreateDirectory(dir);
if (File.Exists(path))
File.Delete(path);
if (!tex.IsReadable())
tex = Texture2DHelpers.ForceReadTexture(tex);
#if CPP
byte[] data = tex.EncodeToPNG();
#else
byte[] data = tex.EncodeToPNGSafe();
#endif
File.WriteAllBytes(path, data);
}
});
// Actual texture viewer
var imageObj = UIFactory.CreateUIObject("TextureViewerImage", parent);
var image = imageObj.AddComponent<Image>();
var sprite = ImageConversionUnstrip.CreateSprite(tex);
image.sprite = sprite;
var fitter = imageObj.AddComponent<ContentSizeFitter>();
fitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
//fitter.horizontalFit = ContentSizeFitter.FitMode.PreferredSize;
var imageLayout = imageObj.AddComponent<LayoutElement>();
imageLayout.preferredHeight = sprite.rect.height;
imageLayout.preferredWidth = sprite.rect.width;
}
internal void ToggleTextureViewer(bool enabled)
{
m_textureViewerObj.SetActive(enabled);
m_filterAreaObj.SetActive(!enabled);
m_memberListObj.SetActive(!enabled);
m_updateRowObj.SetActive(!enabled);
}
public void ConstructInstanceFilters(GameObject parent)
{

View File

@ -10,6 +10,9 @@ using UnityExplorer.Helpers;
using UnityExplorer.UI;
using UnityExplorer.UI.Shared;
using System.Reflection;
#if CPP
using CppDictionary = Il2CppSystem.Collections.IDictionary;
#endif
namespace UnityExplorer.Inspectors.Reflection
{
@ -44,7 +47,11 @@ namespace UnityExplorer.Inspectors.Reflection
}
internal IDictionary RefIDictionary;
#if CPP
internal CppDictionary RefCppDictionary;
#else
internal IDictionary RefCppDictionary = null;
#endif
internal Type m_typeOfKeys;
internal Type m_typeofValues;
@ -52,7 +59,7 @@ namespace UnityExplorer.Inspectors.Reflection
= new List<KeyValuePair<CachePaired, CachePaired>>();
internal readonly KeyValuePair<CachePaired, CachePaired>[] m_displayedEntries
= new KeyValuePair<CachePaired, CachePaired>[ModConfig.Instance.Default_Page_Limit];
= new KeyValuePair<CachePaired, CachePaired>[ExplorerConfig.Instance.Default_Page_Limit];
internal bool m_recacheWanted = true;
@ -65,6 +72,11 @@ namespace UnityExplorer.Inspectors.Reflection
{
RefIDictionary = Value as IDictionary;
#if CPP
try { RefCppDictionary = (Value as Il2CppSystem.Object).TryCast<CppDictionary>(); }
catch { }
#endif
if (m_subContentParent.activeSelf)
{
GetCacheEntries();
@ -129,13 +141,6 @@ namespace UnityExplorer.Inspectors.Reflection
{
var value = RefIDictionary[key];
//if (index >= m_rowHolders.Count)
//{
// AddRowHolder();
//}
//var holder = m_rowHolders[index];
var cacheKey = new CachePaired(index, this, this.RefIDictionary, PairTypes.Key, m_listContent);
cacheKey.CreateIValue(key, this.m_typeOfKeys);
cacheKey.Disable();
@ -206,9 +211,10 @@ namespace UnityExplorer.Inspectors.Reflection
RefreshDisplay();
}
#region CPP fixes
#region CPP fixes
#if CPP
// temp fix for Il2Cpp IDictionary until interfaces are fixed
private IDictionary EnumerateWithReflection()
{
var valueType = Value?.GetType() ?? FallbackType;
@ -222,8 +228,8 @@ namespace UnityExplorer.Inspectors.Reflection
var valueList = new List<object>();
// store entries with reflection
EnumerateWithReflection(keys, keyList);
EnumerateWithReflection(values, valueList);
EnumerateCollection(keys, keyList);
EnumerateCollection(values, valueList);
// make actual mono dictionary
var dict = (IDictionary)Activator.CreateInstance(typeof(Dictionary<,>)
@ -236,7 +242,7 @@ namespace UnityExplorer.Inspectors.Reflection
return dict;
}
private void EnumerateWithReflection(object collection, List<object> list)
private void EnumerateCollection(object collection, List<object> list)
{
// invoke GetEnumerator
var enumerator = collection.GetType().GetMethod("GetEnumerator").Invoke(collection, null);
@ -253,9 +259,9 @@ namespace UnityExplorer.Inspectors.Reflection
}
#endif
#endregion
#endregion
#region UI CONSTRUCTION
#region UI CONSTRUCTION
internal GameObject m_listContent;
internal LayoutElement m_listLayout;
@ -309,6 +315,6 @@ namespace UnityExplorer.Inspectors.Reflection
// m_rowHolders.Add(obj);
//}
#endregion
#endregion
}
}

View File

@ -37,14 +37,7 @@ namespace UnityExplorer.Inspectors.Reflection
if (m_subContentConstructed)
{
// changing types, destroy subcontent
for (int i = 0; i < m_subContentParent.transform.childCount; i++)
{
var child = m_subContentParent.transform.GetChild(i);
GameObject.Destroy(child.gameObject);
}
m_subContentConstructed = false;
DestroySubContent();
}
if (!s_enumNamesCache.ContainsKey(type))
@ -54,13 +47,34 @@ namespace UnityExplorer.Inspectors.Reflection
var list = new List<KeyValuePair<int, string>>();
var set = new HashSet<string>();
foreach (var value in values)
{
var name = value.ToString();
if (set.Contains(name))
continue;
set.Add(name);
list.Add(new KeyValuePair<int, string>((int)value, name));
var backingType = Enum.GetUnderlyingType(type);
int intValue;
try
{
// this approach is necessary, a simple '(int)value' is not sufficient.
var unbox = Convert.ChangeType(value, backingType);
intValue = (int)Convert.ChangeType(unbox, typeof(int));
}
catch (Exception ex)
{
ExplorerCore.LogWarning("[InteractiveEnum] Could not Unbox underlying type " + backingType.Name + " from " + type.FullName);
ExplorerCore.Log(ex.ToString());
continue;
}
list.Add(new KeyValuePair<int, string>(intValue, name));
}
s_enumNamesCache.Add(type, list.ToArray());

View File

@ -37,11 +37,16 @@ namespace UnityExplorer.Inspectors.Reflection
internal IEnumerable RefIEnumerable;
internal IList RefIList;
#if CPP
internal Il2CppSystem.Collections.ICollection CppICollection;
#else
internal ICollection CppICollection = null;
#endif
internal readonly Type m_baseEntryType;
internal readonly List<CacheEnumerated> m_entries = new List<CacheEnumerated>();
internal readonly CacheEnumerated[] m_displayedEntries = new CacheEnumerated[ModConfig.Instance.Default_Page_Limit];
internal readonly CacheEnumerated[] m_displayedEntries = new CacheEnumerated[ExplorerConfig.Instance.Default_Page_Limit];
internal bool m_recacheWanted = true;
public override void OnValueUpdated()
@ -49,6 +54,14 @@ namespace UnityExplorer.Inspectors.Reflection
RefIEnumerable = Value as IEnumerable;
RefIList = Value as IList;
#if CPP
if (Value != null && RefIList == null)
{
try { CppICollection = (Value as Il2CppSystem.Object).TryCast<Il2CppSystem.Collections.ICollection>(); }
catch { }
}
#endif
if (m_subContentParent.activeSelf)
{
GetCacheEntries();
@ -77,8 +90,8 @@ namespace UnityExplorer.Inspectors.Reflection
if (Value != null)
{
string count = "?";
if (m_recacheWanted && RefIList != null)
count = RefIList.Count.ToString();
if (m_recacheWanted && (RefIList != null || CppICollection != null))
count = RefIList?.Count.ToString() ?? CppICollection.Count.ToString();
else if (!m_recacheWanted)
count = m_entries.Count.ToString();
@ -169,89 +182,62 @@ namespace UnityExplorer.Inspectors.Reflection
RefreshDisplay();
}
#region CPP Helpers
#region CPP Helpers
#if CPP
// some temp fixes for Il2Cpp IEnumerables until interfaces are fixed
internal static readonly Dictionary<Type, MethodInfo> s_getEnumeratorMethods = new Dictionary<Type, MethodInfo>();
internal static readonly Dictionary<Type, EnumeratorInfo> s_enumeratorInfos = new Dictionary<Type, EnumeratorInfo>();
internal class EnumeratorInfo
{
internal MethodInfo moveNext;
internal PropertyInfo current;
}
private IEnumerable EnumerateWithReflection()
{
if (Value.IsNullOrDestroyed())
if (Value == null)
return null;
var genericDef = Value.GetType().GetGenericTypeDefinition();
if (genericDef == typeof(Il2CppSystem.Collections.Generic.List<>))
return CppListToMono(genericDef);
else if (genericDef == typeof(Il2CppSystem.Collections.Generic.HashSet<>))
return CppHashSetToMono();
else
return CppIListToMono();
}
// List<T>.ToArray()
private IEnumerable CppListToMono(Type genericTypeDef)
{
if (genericTypeDef == null) return null;
return genericTypeDef
.MakeGenericType(new Type[] { this.m_baseEntryType })
.GetMethod("ToArray")
.Invoke(Value, new object[0]) as IEnumerable;
}
// HashSet.GetEnumerator
private IEnumerable CppHashSetToMono()
{
var set = new HashSet<object>();
// invoke GetEnumerator
var enumerator = Value.GetType().GetMethod("GetEnumerator").Invoke(Value, null);
// get the type of it
var enumeratorType = enumerator.GetType();
// reflect MoveNext and Current
var moveNext = enumeratorType.GetMethod("MoveNext");
var current = enumeratorType.GetProperty("Current");
// iterate
while ((bool)moveNext.Invoke(enumerator, null))
set.Add(current.GetValue(enumerator));
return set;
}
// IList.Item
private IList CppIListToMono()
{
try
// new test
var CppEnumerable = (Value as Il2CppSystem.Object)?.TryCast<Il2CppSystem.Collections.IEnumerable>();
if (CppEnumerable != null)
{
var genericType = typeof(List<>).MakeGenericType(new Type[] { this.m_baseEntryType });
var list = (IList)Activator.CreateInstance(genericType);
var type = Value.GetType();
if (!s_getEnumeratorMethods.ContainsKey(type))
s_getEnumeratorMethods.Add(type, type.GetMethod("GetEnumerator"));
var enumerator = s_getEnumeratorMethods[type].Invoke(Value, null);
var enumeratorType = enumerator.GetType();
for (int i = 0; ; i++)
if (!s_enumeratorInfos.ContainsKey(enumeratorType))
{
try
s_enumeratorInfos.Add(enumeratorType, new EnumeratorInfo
{
var itm = Value?.GetType()
.GetProperty("Item")
.GetValue(Value, new object[] { i });
list.Add(itm);
}
catch { break; }
current = enumeratorType.GetProperty("Current"),
moveNext = enumeratorType.GetMethod("MoveNext"),
});
}
var info = s_enumeratorInfos[enumeratorType];
// iterate
var list = new List<object>();
while ((bool)info.moveNext.Invoke(enumerator, null))
list.Add(info.current.GetValue(enumerator));
return list;
}
catch (Exception e)
{
ExplorerCore.Log("Exception converting Il2Cpp IList to Mono IList: " + e.GetType() + ", " + e.Message);
return null;
}
return null;
}
#endif
#endregion
#endregion
#region UI CONSTRUCTION
#region UI CONSTRUCTION
internal GameObject m_listContent;
internal LayoutElement m_listLayout;
@ -296,6 +282,6 @@ namespace UnityExplorer.Inspectors.Reflection
contentFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
}
#endregion
#endregion
}
}

View File

@ -14,8 +14,9 @@ namespace UnityExplorer.Inspectors.Reflection
{
public InteractiveString(object value, Type valueType) : base(value, valueType) { }
public override bool HasSubContent => false;
public override bool SubContentWanted => false;
public override bool HasSubContent => true;
public override bool SubContentWanted => true;
public override bool WantInspectBtn => false;
public override void OnValueUpdated()
@ -27,10 +28,9 @@ namespace UnityExplorer.Inspectors.Reflection
{
base.OnException(member);
if (m_hiddenObj.gameObject.activeSelf)
if (m_subContentConstructed && m_hiddenObj.gameObject.activeSelf)
m_hiddenObj.gameObject.SetActive(false);
// m_baseLabel.text = DefaultLabel;
m_labelLayout.minWidth = 200;
m_labelLayout.flexibleWidth = 5000;
}
@ -45,39 +45,62 @@ namespace UnityExplorer.Inspectors.Reflection
return;
}
if (!m_hiddenObj.gameObject.activeSelf)
m_hiddenObj.gameObject.SetActive(true);
m_baseLabel.text = m_richValueType;
if (Value != null)
if (m_subContentConstructed)
{
var toString = Value.ToString();
if (!m_hiddenObj.gameObject.activeSelf)
m_hiddenObj.gameObject.SetActive(true);
}
if (!string.IsNullOrEmpty((string)Value))
{
var toString = (string)Value;
if (toString.Length > 15000)
toString = toString.Substring(0, 15000);
m_valueInput.text = toString;
m_placeholderText.text = toString;
m_readonlyInput.text = toString;
if (m_subContentConstructed)
{
m_valueInput.text = toString;
m_placeholderText.text = toString;
}
}
else
{
m_valueInput.text = "";
m_placeholderText.text = "null";
string s = Value == null
? "null"
: "empty";
m_readonlyInput.text = $"<i><color=grey>{s}</color></i>";
if (m_subContentConstructed)
{
m_valueInput.text = "";
m_placeholderText.text = s;
}
}
m_labelLayout.minWidth = 50;
m_labelLayout.flexibleWidth = 0;
}
internal void OnApplyClicked()
{
Value = m_valueInput.text;
Owner.SetValue();
RefreshUIForValue();
}
internal InputField m_valueInput;
// for the default label
internal LayoutElement m_labelLayout;
//internal InputField m_readonlyInput;
internal Text m_readonlyInput;
// for input
internal InputField m_valueInput;
internal GameObject m_hiddenObj;
internal Text m_placeholderText;
@ -90,12 +113,38 @@ namespace UnityExplorer.Inspectors.Reflection
m_labelLayout = m_baseLabel.gameObject.GetComponent<LayoutElement>();
m_hiddenObj = UIFactory.CreateLabel(m_valueContent, TextAnchor.MiddleLeft);
var readonlyInputObj = UIFactory.CreateLabel(m_valueContent, TextAnchor.MiddleLeft);
m_readonlyInput = readonlyInputObj.GetComponent<Text>();
m_readonlyInput.horizontalOverflow = HorizontalWrapMode.Overflow;
var testFitter = readonlyInputObj.AddComponent<ContentSizeFitter>();
testFitter.verticalFit = ContentSizeFitter.FitMode.MinSize;
var labelLayout = readonlyInputObj.AddComponent<LayoutElement>();
labelLayout.minHeight = 25;
labelLayout.preferredHeight = 25;
labelLayout.flexibleHeight = 0;
}
public override void ConstructSubcontent()
{
base.ConstructSubcontent();
var groupObj = UIFactory.CreateVerticalGroup(m_subContentParent, new Color(1, 1, 1, 0));
var group = groupObj.GetComponent<VerticalLayoutGroup>();
group.spacing = 4;
group.padding.top = 3;
group.padding.left = 3;
group.padding.right = 3;
group.padding.bottom = 3;
m_hiddenObj = UIFactory.CreateLabel(groupObj, TextAnchor.MiddleLeft);
m_hiddenObj.SetActive(false);
var hiddenText = m_hiddenObj.GetComponent<Text>();
hiddenText.color = Color.clear;
hiddenText.fontSize = 14;
hiddenText.raycastTarget = false;
hiddenText.supportRichText = false;
var hiddenFitter = m_hiddenObj.AddComponent<ContentSizeFitter>();
hiddenFitter.verticalFit = ContentSizeFitter.FitMode.PreferredSize;
var hiddenLayout = m_hiddenObj.AddComponent<LayoutElement>();
@ -121,19 +170,23 @@ namespace UnityExplorer.Inspectors.Reflection
m_placeholderText = m_valueInput.placeholder.GetComponent<Text>();
m_valueInput.onValueChanged.AddListener((string val) =>
m_placeholderText.supportRichText = false;
m_valueInput.textComponent.supportRichText = false;
m_valueInput.onValueChanged.AddListener((string val) =>
{
hiddenText.text = val;
hiddenText.text = val ?? "";
LayoutRebuilder.ForceRebuildLayoutImmediate(Owner.m_mainRect);
});
if (Owner.CanWrite)
{
var applyBtnObj = UIFactory.CreateButton(m_valueContent, new Color(0.2f, 0.2f, 0.2f));
var applyBtnObj = UIFactory.CreateButton(groupObj, new Color(0.2f, 0.2f, 0.2f));
var applyLayout = applyBtnObj.AddComponent<LayoutElement>();
applyLayout.minWidth = 50;
applyLayout.minHeight = 25;
applyLayout.flexibleWidth = 0;
var applyBtn = applyBtnObj.GetComponent<Button>();
applyBtn.onClick.AddListener(OnApplyClicked);
@ -144,6 +197,8 @@ namespace UnityExplorer.Inspectors.Reflection
{
m_valueInput.readOnly = true;
}
RefreshUIForValue();
}
}
}

View File

@ -226,14 +226,15 @@ namespace UnityExplorer.Inspectors.Reflection
if (StructInfo != null)
{
// changing types, destroy subcontent
for (int i = 0; i < m_subContentParent.transform.childCount; i++)
{
var child = m_subContentParent.transform.GetChild(i);
GameObject.Destroy(child.gameObject);
}
DestroySubContent();
//// changing types, destroy subcontent
//for (int i = 0; i < m_subContentParent.transform.childCount; i++)
//{
// var child = m_subContentParent.transform.GetChild(i);
// GameObject.Destroy(child.gameObject);
//}
m_UIConstructed = false;
//m_UIConstructed = false;
}
m_lastStructType = type;

View File

@ -12,29 +12,47 @@ namespace UnityExplorer.Inspectors.Reflection
{
public class InteractiveValue
{
/// <summary>
/// Get the <see cref="InteractiveValue"/> subclass which supports the provided <paramref name="type"/>.
/// </summary>
/// <param name="type">The <see cref="Type"/> which you want the <see cref="InteractiveValue"/> Type for.</param>
/// <returns>The best subclass of <see cref="InteractiveValue"/> which supports the provided <paramref name="type"/>.</returns>
public static Type GetIValueForType(Type type)
{
// rather ugly but I couldn't think of a cleaner way that was worth it.
// switch-case doesn't really work here.
// arbitrarily check some types, fastest methods first.
if (type == typeof(bool))
return typeof(InteractiveBool);
else if (type == typeof(string))
return typeof(InteractiveString);
// if type is primitive then it must be a number if its not a bool
else if (type.IsPrimitive)
return typeof(InteractiveNumber);
// check for strings
else if (type == typeof(string))
return typeof(InteractiveString);
// check for enum/flags
else if (typeof(Enum).IsAssignableFrom(type))
{
if (type.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] attributes && attributes.Length > 0)
// NET 3.5 doesn't have "GetCustomAttribute", gotta use the multiple version.
if (type.GetCustomAttributes(typeof(FlagsAttribute), true) is object[] fa && fa.Any())
return typeof(InteractiveFlags);
else
return typeof(InteractiveEnum);
}
// check for unity struct types
else if (InteractiveUnityStruct.SupportsType(type))
return typeof(InteractiveUnityStruct);
// check Transform, force InteractiveValue so they dont become InteractiveEnumerables.
else if (typeof(Transform).IsAssignableFrom(type))
return typeof(InteractiveValue);
// check Dictionaries before Enumerables
else if (ReflectionHelpers.IsDictionary(type))
return typeof(InteractiveDictionary);
// finally check for Enumerables
else if (ReflectionHelpers.IsEnumerable(type))
return typeof(InteractiveEnumerable);
// fallback to default
else
return typeof(InteractiveValue);
}
@ -78,7 +96,13 @@ namespace UnityExplorer.Inspectors.Reflection
m_valueContent.SetActive(false);
GameObject.Destroy(this.m_valueContent.gameObject);
}
if (this.m_subContentParent && SubContentWanted)
DestroySubContent();
}
public virtual void DestroySubContent()
{
if (this.m_subContentParent && HasSubContent)
{
for (int i = 0; i < this.m_subContentParent.transform.childCount; i++)
{
@ -87,6 +111,8 @@ namespace UnityExplorer.Inspectors.Reflection
GameObject.Destroy(child.gameObject);
}
}
m_subContentConstructed = false;
}
public virtual void OnValueUpdated()
@ -95,18 +121,16 @@ namespace UnityExplorer.Inspectors.Reflection
ConstructUI(m_mainContentParent, m_subContentParent);
if (Owner is CacheMember ownerMember && !string.IsNullOrEmpty(ownerMember.ReflectionException))
{
OnException(ownerMember);
}
else
{
RefreshUIForValue();
}
}
public virtual void OnException(CacheMember member)
{
m_baseLabel.text = "<color=red>" + member.ReflectionException + "</color>";
if (m_UIConstructed)
m_baseLabel.text = "<color=red>" + member.ReflectionException + "</color>";
Value = null;
}
@ -170,6 +194,10 @@ namespace UnityExplorer.Inspectors.Reflection
ConstructSubcontent();
}
internal MethodInfo m_toStringMethod;
internal MethodInfo m_toStringFormatMethod;
internal bool m_gotToStringMethods;
public string GetDefaultLabel(bool updateType = true)
{
var valueType = Value?.GetType() ?? this.FallbackType;
@ -184,6 +212,7 @@ namespace UnityExplorer.Inspectors.Reflection
string label;
// Two dirty fixes for TextAsset and EventSystem, which can have very long ToString results.
if (Value is TextAsset textAsset)
{
label = textAsset.text;
@ -197,22 +226,50 @@ namespace UnityExplorer.Inspectors.Reflection
{
label = m_richValueType;
}
else
else // For everything else...
{
var toString = (string)valueType.GetMethod("ToString", new Type[0])?.Invoke(Value, null)
?? Value.ToString();
if (!m_gotToStringMethods)
{
m_gotToStringMethods = true;
var fullnametemp = valueType.ToString();
if (fullnametemp.StartsWith("Il2CppSystem"))
fullnametemp = fullnametemp.Substring(6, fullnametemp.Length - 6);
m_toStringMethod = valueType.GetMethod("ToString", new Type[0]);
m_toStringFormatMethod = valueType.GetMethod("ToString", new Type[] { typeof(string) });
var temp = toString.Replace(fullnametemp, "").Trim();
// test format method actually works
try
{
m_toStringFormatMethod.Invoke(Value, new object[] { "F3" });
}
catch
{
m_toStringFormatMethod = null;
}
}
if (string.IsNullOrEmpty(temp))
string toString;
if (m_toStringFormatMethod != null)
toString = (string)m_toStringFormatMethod.Invoke(Value, new object[] { "F3" });
else
toString = (string)m_toStringMethod.Invoke(Value, new object[0]);
string typeName = valueType.FullName;
if (typeName.StartsWith("Il2CppSystem."))
typeName = typeName.Substring(6, typeName.Length - 6);
#if CPP
var cppType = UnhollowerRuntimeLib.Il2CppType.From(valueType);
if (cppType != null && ReflectionHelpers.UnobfuscatedTypeNames.ContainsKey(cppType.FullName))
{
typeName = ReflectionHelpers.UnobfuscatedTypeNames[cppType.FullName];
toString = toString.Replace(cppType.FullName, typeName);
}
#endif
// If the ToString is just the type name, use our syntax highlighted type name instead.
if (toString == typeName)
{
label = m_richValueType;
}
else
else // Otherwise, parse the result and put our highlighted name in.
{
if (toString.Length > 200)
toString = toString.Substring(0, 200) + "...";
@ -297,7 +354,7 @@ namespace UnityExplorer.Inspectors.Reflection
void OnInspectClicked()
{
if (!Value.IsNullOrDestroyed(false))
InspectorManager.Instance.Inspect(this.Value);
InspectorManager.Instance.Inspect(this.Value, this.Owner);
}
m_inspectButton.SetActive(false);

View File

@ -43,6 +43,7 @@ namespace UnityExplorer.Inspectors
"Collider2D.Cast",
"Collider2D.Raycast",
"Texture2D.SetPixelDataImpl",
"Camera.CalculateProjectionMatrixFromPhysicalProperties",
#endif
};
private static readonly HashSet<string> bl_memberNameStartsWith = new HashSet<string>
@ -58,6 +59,8 @@ namespace UnityExplorer.Inspectors
public override string TabLabel => m_targetTypeShortName;
internal CacheObjectBase ParentMember { get; set; }
internal readonly Type m_targetType;
internal readonly string m_targetTypeShortName;
@ -66,7 +69,7 @@ namespace UnityExplorer.Inspectors
// filtered members based on current filters
internal readonly List<CacheMember> m_membersFiltered = new List<CacheMember>();
// actual shortlist of displayed members
internal readonly CacheMember[] m_displayedMembers = new CacheMember[ModConfig.Instance.Default_Page_Limit];
internal readonly CacheMember[] m_displayedMembers = new CacheMember[ExplorerConfig.Instance.Default_Page_Limit];
internal bool m_autoUpdate;
@ -170,11 +173,11 @@ namespace UnityExplorer.Inspectors
{
try
{
var sig = GetSig(member);
//ExplorerCore.Log($"Trying to cache member {sig}...");
//ExplorerCore.Log(member.DeclaringType.FullName + "." + member.Name);
var sig = GetSig(member);
var mi = member as MethodInfo;
var pi = member as PropertyInfo;
var fi = member as FieldInfo;
@ -202,6 +205,8 @@ namespace UnityExplorer.Inspectors
list.Add(new CacheProperty(pi, target, m_scrollContent));
else
list.Add(new CacheField(fi, target, m_scrollContent));
list.Last().ParentInspector = this;
}
catch (Exception e)
{
@ -363,6 +368,10 @@ namespace UnityExplorer.Inspectors
#region UI CONSTRUCTION
internal GameObject m_filterAreaObj;
internal GameObject m_updateRowObj;
internal GameObject m_memberListObj;
internal void ConstructUI()
{
var parent = InspectorManager.Instance.m_inspectorContent;
@ -423,7 +432,7 @@ namespace UnityExplorer.Inspectors
ConstructFilterArea();
ConstructOptionsArea();
ConstructUpdateRow();
}
internal void ConstructFilterArea()
@ -444,6 +453,8 @@ namespace UnityExplorer.Inspectors
filterGroup.padding.top = 4;
filterGroup.padding.bottom = 4;
m_filterAreaObj = filterAreaObj;
// name filter
var nameFilterRowObj = UIFactory.CreateHorizontalGroup(filterAreaObj, new Color(1, 1, 1, 0));
@ -540,7 +551,7 @@ namespace UnityExplorer.Inspectors
btn.colors = colors;
}
internal void ConstructOptionsArea()
internal void ConstructUpdateRow()
{
var optionsRowObj = UIFactory.CreateHorizontalGroup(Content, new Color(1, 1, 1, 0));
var optionsLayout = optionsRowObj.AddComponent<LayoutElement>();
@ -551,6 +562,8 @@ namespace UnityExplorer.Inspectors
optionsGroup.childAlignment = TextAnchor.MiddleLeft;
optionsGroup.spacing = 10;
m_updateRowObj = optionsRowObj;
// update button
var updateButtonObj = UIFactory.CreateButton(optionsRowObj, new Color(0.2f, 0.2f, 0.2f));
@ -578,11 +591,12 @@ namespace UnityExplorer.Inspectors
autoUpdateToggle.isOn = false;
autoUpdateToggle.onValueChanged.AddListener((bool val) => { m_autoUpdate = val; });
}
internal void ConstructMemberList()
{
var scrollobj = UIFactory.CreateScrollView(Content, out m_scrollContent, out m_sliderScroller, new Color(0.05f, 0.05f, 0.05f));
m_memberListObj = scrollobj;
m_scrollContentRect = m_scrollContent.GetComponent<RectTransform>();
var scrollGroup = m_scrollContent.GetComponent<VerticalLayoutGroup>();

View File

@ -55,16 +55,15 @@ namespace UnityExplorer.Inspectors
{
get
{
if (!m_dontDestroyObject)
if (!s_dontDestroyObject)
{
m_dontDestroyObject = new GameObject("DontDestroyMe");
GameObject.DontDestroyOnLoad(m_dontDestroyObject);
s_dontDestroyObject = new GameObject("DontDestroyMe");
GameObject.DontDestroyOnLoad(s_dontDestroyObject);
}
return m_dontDestroyObject;
return s_dontDestroyObject;
}
}
internal static GameObject m_dontDestroyObject;
internal static GameObject s_dontDestroyObject;
public void Init()
{
@ -97,32 +96,21 @@ namespace UnityExplorer.Inspectors
}
}
//#if CPP
// public int GetSceneHandle(string sceneName)
// {
// if (sceneName == "DontDestroyOnLoad")
// return DontDestroyScene;
// for (int i = 0; i < SceneManager.sceneCount; i++)
// {
// var scene = SceneManager.GetSceneAt(i);
// if (scene.name == sceneName)
// return scene.handle;
// }
// return -1;
// }
//#endif
internal void OnSceneChange()
{
m_sceneDropdown.OnCancel(null);
RefreshSceneSelector();
}
//internal void OnSceneChange()
//{
// m_sceneDropdown.OnCancel(null);
// RefreshSceneSelector();
//}
private void RefreshSceneSelector()
{
var names = new List<string>();
var scenes = new List<Scene>();
var newNames = new List<string>();
var newScenes = new List<Scene>();
if (m_currentScenes == null)
m_currentScenes = new Scene[0];
bool anyChange = SceneManager.sceneCount != m_currentScenes.Length - 1;
for (int i = 0; i < SceneManager.sceneCount; i++)
{
@ -131,33 +119,35 @@ namespace UnityExplorer.Inspectors
if (scene == default)
continue;
scenes.Add(scene);
names.Add(scene.name);
if (!anyChange && !m_currentScenes.Any(it => it.GetHandle() == scene.GetHandle()))
anyChange = true;
newScenes.Add(scene);
newNames.Add(scene.name);
}
names.Add("DontDestroyOnLoad");
scenes.Add(DontDestroyScene);
newNames.Add("DontDestroyOnLoad");
newScenes.Add(DontDestroyScene);
m_sceneDropdown.options.Clear();
foreach (string scene in names)
foreach (string scene in newNames)
{
m_sceneDropdown.options.Add(new Dropdown.OptionData { text = scene });
}
if (!names.Contains(m_sceneDropdownText.text))
if (anyChange)
{
m_sceneDropdownText.text = names[0];
SetTargetScene(scenes[0]);
m_sceneDropdown.OnCancel(null);
m_sceneDropdownText.text = newNames[0];
SetTargetScene(newScenes[0]);
SearchPage.Instance.OnSceneChange();
}
m_currentScenes = scenes.ToArray();
m_currentScenes = newScenes.ToArray();
}
//#if CPP
// public void SetTargetScene(string name) => SetTargetScene(scene.handle);
//#endif
public void SetTargetScene(Scene scene)
{
if (scene == default)
@ -167,7 +157,7 @@ namespace UnityExplorer.Inspectors
#if CPP
GameObject[] rootObjs = SceneUnstrip.GetRootGameObjects(scene.handle);
#else
GameObject[] rootObjs = SceneUnstrip.GetRootGameObjects(scene);
GameObject[] rootObjs = scene.GetRootGameObjects();
#endif
SetSceneObjectList(rootObjs);

View File

@ -0,0 +1,41 @@
#if BIE5
using System;
using System.IO;
using System.Reflection;
using BepInEx;
using BepInEx.Logging;
using HarmonyLib;
namespace UnityExplorer
{
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
public class ExplorerBepInPlugin : BaseUnityPlugin, IExplorerLoader
{
public static ExplorerBepInPlugin Instance;
public static ManualLogSource Logging => Instance?.Logger;
public Harmony HarmonyInstance => s_harmony;
private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME);
public string ConfigFolder => Path.Combine(Paths.ConfigPath, ExplorerCore.NAME);
public Action<object> OnLogMessage => (object log) => { Logging?.LogMessage(log?.ToString() ?? ""); };
public Action<object> OnLogWarning => (object log) => { Logging?.LogWarning(log?.ToString() ?? ""); };
public Action<object> OnLogError => (object log) => { Logging?.LogError(log?.ToString() ?? ""); };
internal void Awake()
{
Instance = this;
new ExplorerCore();
}
internal void Update()
{
ExplorerCore.Update();
}
}
}
#endif

View File

@ -1,4 +1,4 @@
#if BIE
#if BIE6
using System;
using System.IO;
using System.Reflection;
@ -18,21 +18,27 @@ namespace UnityExplorer
{
#if MONO
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
public class ExplorerBepInPlugin : BaseUnityPlugin
public class ExplorerBepInPlugin : BaseUnityPlugin, IExplorerLoader
{
public static ExplorerBepInPlugin Instance;
public static ManualLogSource Logging => Instance?.Logger;
public static readonly Harmony HarmonyInstance = new Harmony(ExplorerCore.GUID);
public Harmony HarmonyInstance => s_harmony;
private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME);
public string ConfigFolder => Path.Combine(Paths.ConfigPath, ExplorerCore.NAME);
public Action<object> OnLogMessage => (object log) => { Logging?.LogMessage(log?.ToString() ?? ""); };
public Action<object> OnLogWarning => (object log) => { Logging?.LogWarning(log?.ToString() ?? ""); };
public Action<object> OnLogError => (object log) => { Logging?.LogError(log?.ToString() ?? ""); };
internal void Awake()
{
Instance = this;
new ExplorerCore();
// HarmonyInstance.PatchAll();
}
internal void Update()
@ -44,13 +50,21 @@ namespace UnityExplorer
#if CPP
[BepInPlugin(ExplorerCore.GUID, "UnityExplorer", ExplorerCore.VERSION)]
public class ExplorerBepInPlugin : BasePlugin
public class ExplorerBepInPlugin : BasePlugin, IExplorerLoader
{
public static ExplorerBepInPlugin Instance;
public static ManualLogSource Logging => Instance?.Log;
public static readonly Harmony HarmonyInstance = new Harmony(ExplorerCore.GUID);
public Harmony HarmonyInstance => s_harmony;
private static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
public string ExplorerFolder => Path.Combine(Paths.PluginPath, ExplorerCore.NAME);
public string ConfigFolder => Path.Combine(Paths.ConfigPath, ExplorerCore.NAME);
public Action<object> OnLogMessage => (object log) => { Logging?.LogMessage(log?.ToString() ?? ""); };
public Action<object> OnLogWarning => (object log) => { Logging?.LogWarning(log?.ToString() ?? ""); };
public Action<object> OnLogError => (object log) => { Logging?.LogError(log?.ToString() ?? ""); };
// Init
public override void Load()
@ -67,8 +81,6 @@ namespace UnityExplorer
GameObject.DontDestroyOnLoad(obj);
new ExplorerCore();
// HarmonyInstance.PatchAll();
}
// BepInEx Il2Cpp mod class doesn't have monobehaviour methods yet, so wrap them in a dummy.

View File

@ -0,0 +1,39 @@
#if ML
using System;
using System.IO;
using MelonLoader;
namespace UnityExplorer
{
public class ExplorerMelonMod : MelonMod, IExplorerLoader
{
public static ExplorerMelonMod Instance;
public string ExplorerFolder => Path.Combine("Mods", ExplorerCore.NAME);
public string ConfigFolder => ExplorerFolder;
public Action<object> OnLogMessage => (object log) => { MelonLogger.Msg(log?.ToString() ?? ""); };
public Action<object> OnLogWarning => (object log) => { MelonLogger.Warning(log?.ToString() ?? ""); };
public Action<object> OnLogError => (object log) => { MelonLogger.Error(log?.ToString() ?? ""); };
public Harmony.HarmonyInstance HarmonyInstance => Instance.Harmony;
public override void OnApplicationStart()
{
Instance = this;
new ExplorerCore();
}
public override void OnUpdate()
{
ExplorerCore.Update();
}
//public override void OnSceneWasLoaded(int buildIndex, string sceneName)
//{
// ExplorerCore.Instance.OnSceneLoaded();
//}
}
}
#endif

View File

@ -0,0 +1,105 @@
#if STANDALONE
using HarmonyLib;
using System;
using System.IO;
using System.Reflection;
using UnityEngine;
#if CPP
using UnhollowerRuntimeLib;
#endif
namespace UnityExplorer
{
public class ExplorerStandalone : IExplorerLoader
{
/// <summary>
/// Call this to initialize UnityExplorer. Optionally, also subscribe to the 'OnLog' event to handle logging.
/// </summary>
/// <returns>The new (or active, if one exists) instance of ExplorerStandalone.</returns>
public static ExplorerStandalone CreateInstance()
{
if (Instance != null)
return Instance;
return new ExplorerStandalone();
}
private ExplorerStandalone()
{
Init();
}
public static ExplorerStandalone Instance { get; private set; }
/// <summary>
/// Invoked whenever Explorer logs something. Subscribe to this to handle logging.
/// </summary>
public static event Action<string, LogType> OnLog;
public Harmony HarmonyInstance => s_harmony;
public static readonly Harmony s_harmony = new Harmony(ExplorerCore.GUID);
public string ExplorerFolder
{
get
{
if (s_explorerFolder == null)
{
s_explorerFolder =
Path.Combine(
Path.GetDirectoryName(
Uri.UnescapeDataString(new Uri(Assembly.GetExecutingAssembly().CodeBase)
.AbsolutePath)),
"UnityExplorer");
if (!Directory.Exists(s_explorerFolder))
Directory.CreateDirectory(s_explorerFolder);
}
return s_explorerFolder;
}
}
private static string s_explorerFolder;
public string ConfigFolder => ExplorerFolder;
Action<object> IExplorerLoader.OnLogMessage => (object log) => { OnLog?.Invoke(log?.ToString() ?? "", LogType.Log); };
Action<object> IExplorerLoader.OnLogWarning => (object log) => { OnLog?.Invoke(log?.ToString() ?? "", LogType.Warning); };
Action<object> IExplorerLoader.OnLogError => (object log) => { OnLog?.Invoke(log?.ToString() ?? "", LogType.Error); };
private void Init()
{
Instance = this;
#if CPP
ClassInjector.RegisterTypeInIl2Cpp<ExplorerBehaviour>();
var obj = new GameObject(
"ExplorerBehaviour",
new Il2CppSystem.Type[] { Il2CppType.Of<ExplorerBehaviour>() }
);
#else
var obj = new GameObject(
"ExplorerBehaviour",
new Type[] { typeof(ExplorerBehaviour) }
);
#endif
obj.hideFlags = HideFlags.HideAndDontSave;
GameObject.DontDestroyOnLoad(obj);
new ExplorerCore();
}
public class ExplorerBehaviour : MonoBehaviour
{
#if CPP
public ExplorerBehaviour(IntPtr ptr) : base(ptr) { }
#endif
internal void Update()
{
ExplorerCore.Update();
}
}
}
}
#endif

View File

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer
{
public interface IExplorerLoader
{
string ExplorerFolder { get; }
string ConfigFolder { get; }
Action<object> OnLogMessage { get; }
Action<object> OnLogWarning { get; }
Action<object> OnLogError { get; }
#if ML
Harmony.HarmonyInstance HarmonyInstance { get; }
#else
HarmonyLib.Harmony HarmonyInstance { get; }
#endif
}
}

Binary file not shown.

View File

@ -0,0 +1,47 @@
#if CPP
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.SceneManagement;
using UnityExplorer.Helpers;
namespace UnityExplorer.Runtime.Il2Cpp
{
public class Il2CppProvider : RuntimeProvider
{
public override void Initialize()
{
ReflectionHelpers.TryLoadGameModules();
}
public override void SetupEvents()
{
Application.add_logMessageReceived(
new Action<string, string, LogType>(ExplorerCore.Instance.OnUnityLog));
//SceneManager.add_sceneLoaded(
// new Action<Scene, LoadSceneMode>(ExplorerCore.Instance.OnSceneLoaded1));
//SceneManager.add_activeSceneChanged(
// new Action<Scene, Scene>(ExplorerCore.Instance.OnSceneLoaded2));
}
}
}
public static class UnityEventExtensions
{
public static void AddListener(this UnityEvent action, Action listener)
{
action.AddListener(listener);
}
public static void AddListener<T>(this UnityEvent<T> action, Action<T> listener)
{
action.AddListener(listener);
}
}
#endif

View File

@ -0,0 +1,26 @@
#if MONO
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.SceneManagement;
namespace UnityExplorer.Runtime.Mono
{
public class MonoProvider : RuntimeProvider
{
public override void Initialize()
{
}
public override void SetupEvents()
{
Application.logMessageReceived += ExplorerCore.Instance.OnUnityLog;
//SceneManager.sceneLoaded += ExplorerCore.Instance.OnSceneLoaded1;
//SceneManager.activeSceneChanged += ExplorerCore.Instance.OnSceneLoaded2;
}
}
}
#endif

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace UnityExplorer.Runtime
{
// Work in progress, this will be used to replace all the "if CPP / if MONO"
// pre-processor directives all over the codebase.
public abstract class RuntimeProvider
{
public static RuntimeProvider Instance;
public RuntimeProvider()
{
Initialize();
SetupEvents();
}
public static void Init() =>
#if CPP
Instance = new Il2Cpp.Il2CppProvider();
#else
Instance = new Mono.MonoProvider();
#endif
public abstract void Initialize();
public abstract void SetupEvents();
}
}

View File

@ -3,11 +3,24 @@ using System.Collections.Generic;
using UnityExplorer.UI;
using UnityEngine;
using System;
using System.Runtime.InteropServices;
using System.Text;
using UnityExplorer.Unstrip;
#if CPP
using UnhollowerBaseLib;
using UnityExplorer.Helpers;
#endif
namespace UnityExplorer.Tests
{
internal enum TestByteEnum : byte
{
One,
Two,
Three,
TwoFiftyFive = 255,
}
public static class StaticTestClass
{
public static int StaticProperty => 5;
@ -19,11 +32,18 @@ namespace UnityExplorer.Tests
"three",
};
public static void StaticMethod() { }
}
public class TestClass
{
internal static TestByteEnum testingByte = TestByteEnum.One;
public string AAALongString = @"1
2
3
4
5";
public Vector2 AATestVector2 = new Vector2(1, 2);
public Vector3 AATestVector3 = new Vector3(1, 2, 3);
public Vector4 AATestVector4 = new Vector4(1, 2, 3, 4);
@ -112,13 +132,15 @@ namespace UnityExplorer.Tests
private static bool m_setOnlyProperty;
public static bool ReadSetOnlyProperty => m_setOnlyProperty;
public Texture TestTexture;
public Texture2D TestTexture;
public static Sprite TestSprite;
#if CPP
public static Il2CppSystem.Collections.Generic.HashSet<string> CppHashSetTest;
public static Il2CppSystem.Collections.Generic.List<string> CppStringTest;
public static Il2CppSystem.Collections.IList CppIList;
//public static Il2CppSystem.Collections.Generic.Dictionary<string, string> CppDictTest;
//public static Il2CppSystem.Collections.Generic.Dictionary<int, float> CppDictTest2;
#endif
public TestClass()
@ -134,20 +156,7 @@ namespace UnityExplorer.Tests
}
#if CPP
TestTexture = UIManager.MakeSolidTexture(Color.white, 1000, 600);
TestTexture.name = "TestTexture";
var r = new Rect(0, 0, TestTexture.width, TestTexture.height);
var v2 = Vector2.zero;
var v4 = Vector4.zero;
TestSprite = Sprite.CreateSprite_Injected((Texture2D)TestTexture, ref r, ref v2, 100f, 0u, SpriteMeshType.Tight, ref v4, false);
GameObject.DontDestroyOnLoad(TestTexture);
GameObject.DontDestroyOnLoad(TestSprite);
//// test loading a tex from file
//var dataToLoad = System.IO.File.ReadAllBytes(@"Mods\UnityExplorer\Tex_Nemundis_Nebula.png");
//ExplorerCore.Log($"Tex load success: {TestTexture.LoadImage(dataToLoad, false)}");
TextureSpriteTest();
CppHashSetTest = new Il2CppSystem.Collections.Generic.HashSet<string>();
CppHashSetTest.Add("1");
@ -157,16 +166,45 @@ namespace UnityExplorer.Tests
CppStringTest = new Il2CppSystem.Collections.Generic.List<string>();
CppStringTest.Add("1");
CppStringTest.Add("2");
//CppDictTest = new Il2CppSystem.Collections.Generic.Dictionary<string, string>();
//CppDictTest.Add("key1", "value1");
//CppDictTest.Add("key2", "value2");
//CppDictTest.Add("key3", "value3");
//CppDictTest2 = new Il2CppSystem.Collections.Generic.Dictionary<int, float>();
//CppDictTest2.Add(0, 0.5f);
//CppDictTest2.Add(1, 0.5f);
//CppDictTest2.Add(2, 0.5f);
#endif
}
public static string TestRefInOutGeneric<T>(ref string arg0, in int arg1, out string arg2) where T : Component
private void TextureSpriteTest()
{
arg2 = "this is arg2";
TestTexture = new Texture2D(32, 32, TextureFormat.ARGB32, false)
{
name = "TestTexture"
};
TestSprite = ImageConversionUnstrip.CreateSprite(TestTexture);
return $"T: '{typeof(T).FullName}', ref arg0: '{arg0}', in arg1: '{arg1}', out arg2: '{arg2}'";
GameObject.DontDestroyOnLoad(TestTexture);
GameObject.DontDestroyOnLoad(TestSprite);
// test loading a tex from file
if (System.IO.File.Exists(@"D:\Downloads\test.png"))
{
var dataToLoad = System.IO.File.ReadAllBytes(@"D:\Downloads\test.png");
ExplorerCore.Log($"Tex load success: {TestTexture.LoadImage(dataToLoad, false)}");
}
}
//public static string TestRefInOutGeneric<T>(ref string arg0, in int arg1, out string arg2) where T : Component
//{
// arg2 = "this is arg2";
// return $"T: '{typeof(T).FullName}', ref arg0: '{arg0}', in arg1: '{arg1}', out arg2: '{arg2}'";
//}
// test a non-generic dictionary
public Hashtable TestNonGenericDict()

View File

@ -28,7 +28,7 @@ namespace UnityExplorer.UI
UpdateCursorControl();
}
public static bool ShouldForceMouse => ExplorerCore.ShowMenu && Unlock;
public static bool ShouldForceMouse => UIManager.ShowMenu && Unlock;
private static CursorLockMode m_lastLockMode;
private static bool m_lastVisibleState;
@ -42,7 +42,7 @@ namespace UnityExplorer.UI
public static void Init()
{
ModConfig.OnConfigChanged += ModConfig_OnConfigChanged;
ExplorerConfig.OnConfigChanged += ModConfig_OnConfigChanged;
SetupPatches();
@ -51,7 +51,7 @@ namespace UnityExplorer.UI
internal static void ModConfig_OnConfigChanged()
{
Unlock = ModConfig.Instance.Force_Unlock_Mouse;
Unlock = ExplorerConfig.Instance.Force_Unlock_Mouse;
}
private static void SetupPatches()
@ -87,12 +87,6 @@ namespace UnityExplorer.UI
new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_set_visible))),
true);
#if BIE
#if CPP
// temporarily disabling this patch in BepInEx il2cpp as it's causing a crash in some games.
return;
#endif
#endif
TryPatch(typeof(EventSystem),
"current",
new HarmonyMethod(typeof(ForceUnlockCursor).GetMethod(nameof(Prefix_EventSystem_set_current))),
@ -108,12 +102,7 @@ namespace UnityExplorer.UI
{
try
{
var harmony =
#if ML
ExplorerMelonMod.Instance.harmonyInstance;
#else
ExplorerBepInPlugin.HarmonyInstance;
#endif
var harmony = ExplorerCore.Loader.HarmonyInstance;
System.Reflection.PropertyInfo prop = type.GetProperty(property);
@ -164,11 +153,25 @@ namespace UnityExplorer.UI
public static void SetEventSystem()
{
// temp disabled for new InputSystem
if (InputManager.CurrentType == InputType.InputSystem)
return;
// Disable current event system object
if (m_lastEventSystem || EventSystem.current)
{
if (!m_lastEventSystem)
m_lastEventSystem = EventSystem.current;
//ExplorerCore.Log("Disabling current event system...");
m_lastEventSystem.enabled = false;
//m_lastEventSystem.gameObject.SetActive(false);
}
// Set to our current system
m_settingEventSystem = true;
EventSystem.current = UIManager.EventSys;
UIManager.EventSys.enabled = true;
InputManager.ActivateUIModule();
m_settingEventSystem = false;
}
@ -180,6 +183,9 @@ namespace UnityExplorer.UI
if (m_lastEventSystem)
{
m_lastEventSystem.enabled = true;
//m_lastEventSystem.gameObject.SetActive(true);
m_settingEventSystem = true;
EventSystem.current = m_lastEventSystem;
m_lastInputModule?.ActivateModule();
@ -195,7 +201,7 @@ namespace UnityExplorer.UI
m_lastEventSystem = value;
m_lastInputModule = value?.currentInputModule;
if (ExplorerCore.ShowMenu)
if (UIManager.ShowMenu)
{
value = UIManager.EventSys;
}

View File

@ -155,8 +155,11 @@ namespace UnityExplorer.UI
MainPanel = UIFactory.CreatePanel(UIManager.CanvasRoot, "MainMenu", out GameObject content);
RectTransform panelRect = MainPanel.GetComponent<RectTransform>();
panelRect.anchorMin = new Vector2(0.25f, 0.1f);
panelRect.anchorMax = new Vector2(0.78f, 0.95f);
//panelRect.anchorMin = new Vector2(0.25f, 0.1f);
//panelRect.anchorMax = new Vector2(0.78f, 0.95f);
var anchors = ExplorerConfig.Instance.GetWindowAnchorsVector();
panelRect.anchorMin = new Vector2(anchors.x, anchors.y);
panelRect.anchorMax = new Vector2(anchors.z, anchors.w);
MainPanel.AddComponent<Mask>();
@ -208,7 +211,7 @@ namespace UnityExplorer.UI
GameObject hideBtnObj = UIFactory.CreateButton(titleBar);
Button hideBtn = hideBtnObj.GetComponent<Button>();
hideBtn.onClick.AddListener(() => { ExplorerCore.ShowMenu = false; });
hideBtn.onClick.AddListener(() => { UIManager.ShowMenu = false; });
ColorBlock colorBlock = hideBtn.colors;
colorBlock.normalColor = new Color(65f / 255f, 23f / 255f, 23f / 255f);
colorBlock.pressedColor = new Color(35f / 255f, 10f / 255f, 10f / 255f);
@ -224,13 +227,13 @@ namespace UnityExplorer.UI
hideText.resizeTextForBestFit = true;
hideText.resizeTextMinSize = 8;
hideText.resizeTextMaxSize = 14;
hideText.text = $"Hide ({ModConfig.Instance.Main_Menu_Toggle})";
hideText.text = $"Hide ({ExplorerConfig.Instance.Main_Menu_Toggle})";
ModConfig.OnConfigChanged += ModConfig_OnConfigChanged;
ExplorerConfig.OnConfigChanged += ModConfig_OnConfigChanged;
void ModConfig_OnConfigChanged()
{
hideText.text = $"Hide ({ModConfig.Instance.Main_Menu_Toggle})";
hideText.text = $"Hide ({ExplorerConfig.Instance.Main_Menu_Toggle})";
}
}

View File

@ -15,8 +15,8 @@ namespace UnityExplorer.UI.Modules
{
public static DebugConsole Instance { get; private set; }
public static bool LogUnity { get; set; } = ModConfig.Instance.Log_Unity_Debug;
public static bool SaveToDisk { get; set; } = ModConfig.Instance.Save_Logs_To_Disk;
public static bool LogUnity { get; set; } = ExplorerConfig.Instance.Log_Unity_Debug;
//public static bool SaveToDisk { get; set; } = ModConfig.Instance.Save_Logs_To_Disk;
internal static StreamWriter s_streamWriter;
@ -49,8 +49,8 @@ namespace UnityExplorer.UI.Modules
// set up IO
if (!SaveToDisk)
return;
//if (!SaveToDisk)
// return;
var path = ExplorerCore.EXPLORER_FOLDER + @"\Logs";
@ -69,7 +69,7 @@ namespace UnityExplorer.UI.Modules
}
var fileName = "UnityExplorer " + DateTime.Now.ToString("u") + ".txt";
fileName = ExplorerCore.RemoveInvalidFilenameChars(fileName);
fileName = RemoveInvalidFilenameChars(fileName);
var stream = File.Create(path + @"\" + fileName);
s_streamWriter = new StreamWriter(stream)
@ -81,6 +81,16 @@ namespace UnityExplorer.UI.Modules
s_streamWriter.WriteLine(msg);
}
public static string RemoveInvalidFilenameChars(string s)
{
var invalid = Path.GetInvalidFileNameChars();
foreach (var c in invalid)
{
s = s.Replace(c.ToString(), "");
}
return s;
}
public static void Log(string message)
{
Log(message, null);
@ -256,8 +266,8 @@ namespace UnityExplorer.UI.Modules
void ToggleLogUnity(bool val)
{
LogUnity = val;
ModConfig.Instance.Log_Unity_Debug = val;
ModConfig.SaveSettings();
ExplorerConfig.Instance.Log_Unity_Debug = val;
ExplorerConfig.SaveSettings();
}
var unityToggleLayout = unityToggleObj.AddComponent<LayoutElement>();

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
//using TMPro;
using UnityEngine;
using UnityEngine.UI;
using UnityExplorer.Config;
@ -19,6 +18,7 @@ namespace UnityExplorer.UI.Modules
private Toggle m_unlockMouseToggle;
private InputField m_pageLimitInput;
private InputField m_defaultOutputInput;
private Toggle m_hideOnStartupToggle;
public override void Init()
{
@ -27,29 +27,24 @@ namespace UnityExplorer.UI.Modules
public override void Update()
{
// not needed?
}
internal void OnApply()
{
if (!string.IsNullOrEmpty(m_keycodeInput.text) && Enum.Parse(typeof(KeyCode), m_keycodeInput.text) is KeyCode keyCode)
{
ModConfig.Instance.Main_Menu_Toggle = keyCode;
}
ExplorerConfig.Instance.Main_Menu_Toggle = keyCode;
ModConfig.Instance.Force_Unlock_Mouse = m_unlockMouseToggle.isOn;
ExplorerConfig.Instance.Force_Unlock_Mouse = m_unlockMouseToggle.isOn;
if (!string.IsNullOrEmpty(m_pageLimitInput.text) && int.TryParse(m_pageLimitInput.text, out int lim))
{
ModConfig.Instance.Default_Page_Limit = lim;
}
ExplorerConfig.Instance.Default_Page_Limit = lim;
ModConfig.Instance.Default_Output_Path = m_defaultOutputInput.text;
ExplorerConfig.Instance.Default_Output_Path = m_defaultOutputInput.text;
// todo default output path
ExplorerConfig.Instance.Hide_On_Startup = m_hideOnStartupToggle.isOn;
ModConfig.SaveSettings();
ModConfig.InvokeConfigChanged();
ExplorerConfig.SaveSettings();
ExplorerConfig.InvokeConfigChanged();
}
#region UI CONSTRUCTION
@ -98,6 +93,7 @@ namespace UnityExplorer.UI.Modules
ConstructMouseUnlockOpt(optionsGroupObj);
ConstructPageLimitOpt(optionsGroupObj);
ConstructOutputPathOpt(optionsGroupObj);
ConstructHideOnStartupOpt(optionsGroupObj);
var applyBtnObj = UIFactory.CreateButton(Content, new Color(0.2f, 0.2f, 0.2f));
var applyText = applyBtnObj.GetComponentInChildren<Text>();
@ -113,10 +109,34 @@ namespace UnityExplorer.UI.Modules
applyBtn.onClick.AddListener(OnApply);
}
private void ConstructHideOnStartupOpt(GameObject optionsGroupObj)
{
var rowObj = UIFactory.CreateHorizontalGroup(optionsGroupObj, new Color(1, 1, 1, 0));
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
rowGroup.childControlWidth = true;
rowGroup.childForceExpandWidth = false;
rowGroup.childControlHeight = true;
rowGroup.childForceExpandHeight = true;
var groupLayout = rowObj.AddComponent<LayoutElement>();
groupLayout.minHeight = 25;
groupLayout.flexibleHeight = 0;
groupLayout.minWidth = 200;
groupLayout.flexibleWidth = 1000;
var labelObj = UIFactory.CreateLabel(rowObj, TextAnchor.MiddleLeft);
var labelText = labelObj.GetComponent<Text>();
labelText.text = "Hide UI on startup:";
var labelLayout = labelObj.AddComponent<LayoutElement>();
labelLayout.minWidth = 150;
labelLayout.minHeight = 25;
UIFactory.CreateToggle(rowObj, out m_hideOnStartupToggle, out Text toggleText);
m_hideOnStartupToggle.isOn = ExplorerConfig.Instance.Hide_On_Startup;
toggleText.text = "";
}
internal void ConstructKeycodeOpt(GameObject parent)
{
//public KeyCode Main_Menu_Toggle = KeyCode.F7;
var rowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
rowGroup.childControlWidth = true;
@ -139,15 +159,13 @@ namespace UnityExplorer.UI.Modules
var keycodeInputObj = UIFactory.CreateInputField(rowObj);
m_keycodeInput = keycodeInputObj.GetComponent<InputField>();
m_keycodeInput.text = ModConfig.Instance.Main_Menu_Toggle.ToString();
m_keycodeInput.text = ExplorerConfig.Instance.Main_Menu_Toggle.ToString();
m_keycodeInput.placeholder.gameObject.GetComponent<Text>().text = "KeyCode, eg. F7";
}
internal void ConstructMouseUnlockOpt(GameObject parent)
{
//public bool Force_Unlock_Mouse = true;
var rowObj = UIFactory.CreateHorizontalGroup(parent, new Color(1, 1, 1, 0));
var rowGroup = rowObj.GetComponent<HorizontalLayoutGroup>();
rowGroup.childControlWidth = true;
@ -168,7 +186,7 @@ namespace UnityExplorer.UI.Modules
labelLayout.minHeight = 25;
UIFactory.CreateToggle(rowObj, out m_unlockMouseToggle, out Text toggleText);
m_unlockMouseToggle.isOn = ModConfig.Instance.Force_Unlock_Mouse;
m_unlockMouseToggle.isOn = ExplorerConfig.Instance.Force_Unlock_Mouse;
toggleText.text = "";
}
@ -198,7 +216,7 @@ namespace UnityExplorer.UI.Modules
var inputObj = UIFactory.CreateInputField(rowObj);
m_pageLimitInput = inputObj.GetComponent<InputField>();
m_pageLimitInput.text = ModConfig.Instance.Default_Page_Limit.ToString();
m_pageLimitInput.text = ExplorerConfig.Instance.Default_Page_Limit.ToString();
m_pageLimitInput.placeholder.gameObject.GetComponent<Text>().text = "Integer, eg. 20";
}
@ -229,7 +247,7 @@ namespace UnityExplorer.UI.Modules
var inputObj = UIFactory.CreateInputField(rowObj);
m_defaultOutputInput = inputObj.GetComponent<InputField>();
m_defaultOutputInput.text = ModConfig.Instance.Default_Output_Path.ToString();
m_defaultOutputInput.text = ExplorerConfig.Instance.Default_Output_Path.ToString();
m_defaultOutputInput.placeholder.gameObject.GetComponent<Text>().text = @"Directory, eg. Mods\UnityExplorer";
}

View File

@ -1,7 +1,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
//using TMPro;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityEngine.UI;
@ -9,6 +8,7 @@ using UnityExplorer.Helpers;
using UnityExplorer.Inspectors;
using UnityExplorer.UI.Shared;
using UnityExplorer.Unstrip;
using System.Reflection;
#if CPP
using UnhollowerRuntimeLib;
#endif
@ -21,7 +21,7 @@ namespace UnityExplorer.UI.Modules
GameObject,
Component,
Custom,
Instance,
Singleton,
StaticClass
}
@ -46,11 +46,14 @@ namespace UnityExplorer.UI.Modules
public static SearchPage Instance;
internal SearchContext m_context;
private SceneFilter m_sceneFilter;
private ChildFilter m_childFilter;
// ui elements
private Text m_resultCountText;
internal SearchContext m_context;
private InputField m_customTypeInput;
private InputField m_nameInput;
@ -60,9 +63,6 @@ namespace UnityExplorer.UI.Modules
private Dropdown m_sceneDropdown;
private int m_lastSceneCount = -1;
private SceneFilter m_sceneFilter;
private ChildFilter m_childFilter;
private GameObject m_extraFilterRow;
@ -109,6 +109,9 @@ namespace UnityExplorer.UI.Modules
private void RefreshResultList()
{
if (m_resultListPageHandler == null || m_results == null)
return;
m_resultListPageHandler.ListCount = m_results.Length;
int newCount = 0;
@ -132,10 +135,9 @@ namespace UnityExplorer.UI.Modules
else
{
var obj = m_results[itemIndex];
var unityObj = obj as UnityEngine.Object;
var uObj = obj as UnityEngine.Object;
if (obj == null || (uObj != null && !uObj))
if (obj == null || (unityObj != null && !unityObj))
continue;
if (i >= m_resultShortList.Count)
@ -150,17 +152,25 @@ namespace UnityExplorer.UI.Modules
var text = m_resultListTexts[i];
var name = $"<color={UISyntaxHighlight.Class_Instance}>{ReflectionHelpers.GetActualType(obj).Name}</color>";
if (m_context != SearchContext.Instance && m_context != SearchContext.StaticClass)
if (m_context != SearchContext.StaticClass)
{
if (uObj && !string.IsNullOrEmpty(uObj.name))
name += $": {uObj.name}";
else
name += ": <i><color=grey>untitled</color></i>";
}
var name = UISyntaxHighlight.ParseFullSyntax(obj.GetActualType(), true);
text.text = name;
if (unityObj && m_context != SearchContext.Singleton)
{
if (unityObj && !string.IsNullOrEmpty(unityObj.name))
name += $": {unityObj.name}";
else
name += ": <i><color=grey>untitled</color></i>";
}
text.text = name;
}
else
{
var type = obj as Type;
text.text = UISyntaxHighlight.ParseFullSyntax(type, true);
}
var label = text.transform.parent.parent.gameObject;
if (!label.activeSelf)
@ -222,14 +232,127 @@ namespace UnityExplorer.UI.Modules
}
m_sceneDropdown.transform.Find("Label").GetComponent<Text>().text = "Any";
m_sceneFilter = SceneFilter.Any;
}
// ~~~~~ UI Callbacks ~~~~~
internal void OnUnitySearchClicked()
internal void OnSearchClicked()
{
m_resultListPageHandler.CurrentPage = 0;
if (m_context == SearchContext.StaticClass)
StaticClassSearch();
else if (m_context == SearchContext.Singleton)
SingletonSearch();
else
UnityObjectSearch();
RefreshResultList();
if (m_results.Length > 0)
m_resultCountText.text = $"{m_results.Length} Results";
else
m_resultCountText.text = "No results...";
}
internal void StaticClassSearch()
{
var list = new List<Type>();
var nameFilter = "";
if (!string.IsNullOrEmpty(m_nameInput.text))
nameFilter = m_nameInput.text.ToLower();
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var type in asm.TryGetTypes().Where(it => it.IsSealed && it.IsAbstract))
{
if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ToLower().Contains(nameFilter))
continue;
list.Add(type);
}
}
m_results = list.ToArray();
}
internal string[] s_instanceNames = new string[]
{
"m_instance",
"m_Instance",
"s_instance",
"s_Instance",
"_instance",
"_Instance",
"instance",
"Instance",
"<Instance>k__BackingField",
"<instance>k__BackingField",
};
private void SingletonSearch()
{
var instances = new List<object>();
var nameFilter = "";
if (!string.IsNullOrEmpty(m_nameInput.text))
nameFilter = m_nameInput.text.ToLower();
var flags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static;
foreach (var asm in AppDomain.CurrentDomain.GetAssemblies())
{
// Search all non-static, non-enum classes.
foreach (var type in asm.TryGetTypes().Where(it => !(it.IsSealed && it.IsAbstract) && !it.IsEnum))
{
try
{
if (!string.IsNullOrEmpty(nameFilter) && !type.FullName.ToLower().Contains(nameFilter))
continue;
#if CPP
// Only look for Properties in IL2CPP, not for Mono.
PropertyInfo pi;
foreach (var name in s_instanceNames)
{
pi = type.GetProperty(name, flags);
if (pi != null)
{
var instance = pi.GetValue(null, null);
if (instance != null)
{
instances.Add(instance);
continue;
}
}
}
#endif
// Look for a typical Instance backing field.
FieldInfo fi;
foreach (var name in s_instanceNames)
{
fi = type.GetField(name, flags);
if (fi != null)
{
var instance = fi.GetValue(null);
if (instance != null)
{
instances.Add(instance);
break;
}
}
}
}
catch { }
}
}
m_results = instances.ToArray();
}
internal void UnityObjectSearch()
{
Type searchType = null;
switch (m_context)
{
@ -260,11 +383,8 @@ namespace UnityExplorer.UI.Modules
if (searchType == null)
return;
#if MONO
var allObjects = ResourcesUnstrip.FindObjectsOfTypeAll(searchType);
#else
var allObjects = ResourcesUnstrip.FindObjectsOfTypeAll(Il2CppType.From(searchType));
#endif
var results = new List<object>();
// perform filter comparers
@ -308,12 +428,12 @@ namespace UnityExplorer.UI.Modules
: obj.TryCast<Component>().gameObject;
#endif
if (!go)
continue;
// scene check
if (m_sceneFilter != SceneFilter.Any)
{
if (!go)
continue;
switch (m_context)
{
case SearchContext.GameObject:
@ -328,24 +448,23 @@ namespace UnityExplorer.UI.Modules
}
}
// root object check (no parent)
if (m_childFilter == ChildFilter.HasParent && !go.transform.parent)
continue;
else if (m_childFilter == ChildFilter.RootObject && go.transform.parent)
continue;
if (m_childFilter != ChildFilter.Any)
{
if (!go)
continue;
// root object check (no parent)
if (m_childFilter == ChildFilter.HasParent && !go.transform.parent)
continue;
else if (m_childFilter == ChildFilter.RootObject && go.transform.parent)
continue;
}
}
results.Add(obj);
}
m_results = results.ToArray();
if (m_results.Length > 0)
m_resultCountText.text = $"{m_results.Length} Results";
else
m_resultCountText.text = "No results...";
RefreshResultList();
}
private void OnResultPageTurn()
@ -493,6 +612,23 @@ namespace UnityExplorer.UI.Modules
m_customTypeInput = customTypeObj.GetComponent<InputField>();
m_customTypeInput.placeholder.gameObject.GetComponent<Text>().text = "eg. UnityEngine.Texture2D, etc...";
// static class and singleton buttons
var secondRow = UIFactory.CreateHorizontalGroup(optionsGroupObj, new Color(1, 1, 1, 0));
var secondGroup = secondRow.GetComponent<HorizontalLayoutGroup>();
secondGroup.childForceExpandWidth = false;
secondGroup.childForceExpandHeight = false;
secondGroup.spacing = 3;
var secondLayout = secondRow.AddComponent<LayoutElement>();
secondLayout.minHeight = 25;
var spacer = UIFactory.CreateUIObject("spacer", secondRow);
var spaceLayout = spacer.AddComponent<LayoutElement>();
spaceLayout.minWidth = 125;
spaceLayout.minHeight = 25;
AddContextButton(secondRow, "Static Class", SearchContext.StaticClass);
AddContextButton(secondRow, "Singleton", SearchContext.Singleton);
// search input
var nameRowObj = UIFactory.CreateHorizontalGroup(optionsGroupObj, new Color(1, 1, 1, 0));
@ -604,7 +740,7 @@ namespace UnityExplorer.UI.Modules
searchBtnLayout.flexibleHeight = 0;
var searchBtn = searchBtnObj.GetComponent<Button>();
searchBtn.onClick.AddListener(OnUnitySearchClicked);
searchBtn.onClick.AddListener(OnSearchClicked);
}
internal void AddContextButton(GameObject parent, string label, SearchContext context, float width = 110)

View File

@ -33,6 +33,8 @@ namespace UnityExplorer.UI.Shared
this.sliderScroller = sliderScroller;
this.inputField = inputField;
sliderScroller.m_parentInputScroller = this;
inputField.onValueChanged.AddListener(OnTextChanged);
inputRect = inputField.GetComponent<RectTransform>();
@ -68,6 +70,17 @@ namespace UnityExplorer.UI.Shared
}
}
internal bool CheckDestroyed()
{
if (sliderScroller == null || sliderScroller.CheckDestroyed())
{
Instances.Remove(this);
return true;
}
return false;
}
internal void OnTextChanged(string text)
{
m_lastText = text;

View File

@ -21,7 +21,7 @@ namespace UnityExplorer.UI.Shared
{
public PageHandler(SliderScrollbar scroll)
{
ItemsPerPage = ModConfig.Instance?.Default_Page_Limit ?? 20;
ItemsPerPage = ExplorerConfig.Instance?.Default_Page_Limit ?? 20;
m_scrollbar = scroll;
}

View File

@ -8,150 +8,154 @@ using UnityExplorer;
using UnityExplorer.Helpers;
using UnityExplorer.UI;
// Basically just to fix an issue with Scrollbars, instead we use a Slider as the scrollbar.
public class SliderScrollbar
namespace UnityExplorer.UI.Shared
{
internal static readonly List<SliderScrollbar> Instances = new List<SliderScrollbar>();
public bool IsActive { get; private set; }
internal readonly Scrollbar m_scrollbar;
internal readonly Slider m_slider;
internal readonly RectTransform m_scrollRect;
public SliderScrollbar(Scrollbar scrollbar, Slider slider)
// Basically just to fix an issue with Scrollbars, instead we use a Slider as the scrollbar.
public class SliderScrollbar
{
Instances.Add(this);
internal static readonly List<SliderScrollbar> Instances = new List<SliderScrollbar>();
this.m_scrollbar = scrollbar;
this.m_slider = slider;
this.m_scrollRect = scrollbar.transform.parent.GetComponent<RectTransform>();
public bool IsActive { get; private set; }
this.m_scrollbar.onValueChanged.AddListener(this.OnScrollbarValueChanged);
this.m_slider.onValueChanged.AddListener(this.OnSliderValueChanged);
internal readonly Scrollbar m_scrollbar;
internal readonly Slider m_slider;
internal readonly RectTransform m_scrollRect;
this.RefreshVisibility();
this.m_slider.Set(1f, false);
}
internal InputFieldScroller m_parentInputScroller;
internal bool CheckDestroyed()
{
if (!m_slider || !m_scrollbar)
public SliderScrollbar(Scrollbar scrollbar, Slider slider)
{
Instances.Remove(this);
return true;
Instances.Add(this);
this.m_scrollbar = scrollbar;
this.m_slider = slider;
this.m_scrollRect = scrollbar.transform.parent.GetComponent<RectTransform>();
this.m_scrollbar.onValueChanged.AddListener(this.OnScrollbarValueChanged);
this.m_slider.onValueChanged.AddListener(this.OnSliderValueChanged);
this.RefreshVisibility();
this.m_slider.Set(1f, false);
}
return false;
}
internal void Update()
{
this.RefreshVisibility();
}
internal void RefreshVisibility()
{
if (!m_slider.gameObject.activeInHierarchy)
internal bool CheckDestroyed()
{
IsActive = false;
return;
if (!m_slider || !m_scrollbar)
{
Instances.Remove(this);
return true;
}
return false;
}
bool shouldShow = !Mathf.Approximately(this.m_scrollbar.size, 1);
var obj = this.m_slider.handleRect.gameObject;
if (IsActive != shouldShow)
internal void Update()
{
IsActive = shouldShow;
obj.SetActive(IsActive);
if (IsActive)
this.m_slider.Set(this.m_scrollbar.value, false);
else
m_slider.Set(1f, false);
this.RefreshVisibility();
}
internal void RefreshVisibility()
{
if (!m_slider.gameObject.activeInHierarchy)
{
IsActive = false;
return;
}
bool shouldShow = !Mathf.Approximately(this.m_scrollbar.size, 1);
var obj = this.m_slider.handleRect.gameObject;
if (IsActive != shouldShow)
{
IsActive = shouldShow;
obj.SetActive(IsActive);
if (IsActive)
this.m_slider.Set(this.m_scrollbar.value, false);
else
m_slider.Set(1f, false);
}
}
public void OnScrollbarValueChanged(float _value)
{
if (this.m_slider.value != _value)
this.m_slider.Set(_value, false);
}
public void OnSliderValueChanged(float _value)
{
this.m_scrollbar.value = _value;
}
#region UI CONSTRUCTION
public static GameObject CreateSliderScrollbar(GameObject parent, out Slider slider)
{
GameObject sliderObj = UIFactory.CreateUIObject("Slider", parent, UIFactory.thinSize);
GameObject bgObj = UIFactory.CreateUIObject("Background", sliderObj);
GameObject fillAreaObj = UIFactory.CreateUIObject("Fill Area", sliderObj);
GameObject fillObj = UIFactory.CreateUIObject("Fill", fillAreaObj);
GameObject handleSlideAreaObj = UIFactory.CreateUIObject("Handle Slide Area", sliderObj);
GameObject handleObj = UIFactory.CreateUIObject("Handle", handleSlideAreaObj);
Image bgImage = bgObj.AddComponent<Image>();
bgImage.type = Image.Type.Sliced;
bgImage.color = new Color(0.05f, 0.05f, 0.05f, 1.0f);
RectTransform bgRect = bgObj.GetComponent<RectTransform>();
bgRect.anchorMin = Vector2.zero;
bgRect.anchorMax = Vector2.one;
bgRect.sizeDelta = Vector2.zero;
bgRect.offsetMax = new Vector2(-10f, 0f);
RectTransform fillAreaRect = fillAreaObj.GetComponent<RectTransform>();
fillAreaRect.anchorMin = new Vector2(0f, 0.25f);
fillAreaRect.anchorMax = new Vector2(1f, 0.75f);
fillAreaRect.anchoredPosition = new Vector2(-5f, 0f);
fillAreaRect.sizeDelta = new Vector2(-20f, 0f);
Image fillImage = fillObj.AddComponent<Image>();
fillImage.type = Image.Type.Sliced;
fillImage.color = Color.clear;
fillObj.GetComponent<RectTransform>().sizeDelta = new Vector2(10f, 0f);
RectTransform handleSlideRect = handleSlideAreaObj.GetComponent<RectTransform>();
handleSlideRect.anchorMin = new Vector2(0f, 0f);
handleSlideRect.anchorMax = new Vector2(1f, 1f);
handleSlideRect.offsetMin = new Vector2(15f, 30f);
handleSlideRect.offsetMax = new Vector2(-15f, 0f);
handleSlideRect.sizeDelta = new Vector2(-30f, -30f);
Image handleImage = handleObj.AddComponent<Image>();
handleImage.color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
var handleRect = handleObj.GetComponent<RectTransform>();
handleRect.sizeDelta = new Vector2(15f, 30f);
handleRect.offsetMin = new Vector2(-13f, -28f);
handleRect.offsetMax = new Vector2(3f, -2f);
var sliderBarLayout = sliderObj.AddComponent<LayoutElement>();
sliderBarLayout.minWidth = 25;
sliderBarLayout.flexibleWidth = 0;
sliderBarLayout.minHeight = 30;
sliderBarLayout.flexibleHeight = 5000;
slider = sliderObj.AddComponent<Slider>();
slider.fillRect = fillObj.GetComponent<RectTransform>();
slider.handleRect = handleObj.GetComponent<RectTransform>();
slider.targetGraphic = handleImage;
slider.direction = Slider.Direction.BottomToTop;
UIFactory.SetDefaultColorTransitionValues(slider);
return sliderObj;
}
#endregion
}
public void OnScrollbarValueChanged(float _value)
{
if (this.m_slider.value != _value)
this.m_slider.Set(_value, false);
}
public void OnSliderValueChanged(float _value)
{
this.m_scrollbar.value = _value;
}
#region UI CONSTRUCTION
public static GameObject CreateSliderScrollbar(GameObject parent, out Slider slider)
{
GameObject sliderObj = UIFactory.CreateUIObject("Slider", parent, UIFactory.thinSize);
GameObject bgObj = UIFactory.CreateUIObject("Background", sliderObj);
GameObject fillAreaObj = UIFactory.CreateUIObject("Fill Area", sliderObj);
GameObject fillObj = UIFactory.CreateUIObject("Fill", fillAreaObj);
GameObject handleSlideAreaObj = UIFactory.CreateUIObject("Handle Slide Area", sliderObj);
GameObject handleObj = UIFactory.CreateUIObject("Handle", handleSlideAreaObj);
Image bgImage = bgObj.AddComponent<Image>();
bgImage.type = Image.Type.Sliced;
bgImage.color = new Color(0.05f, 0.05f, 0.05f, 1.0f);
RectTransform bgRect = bgObj.GetComponent<RectTransform>();
bgRect.anchorMin = Vector2.zero;
bgRect.anchorMax = Vector2.one;
bgRect.sizeDelta = Vector2.zero;
bgRect.offsetMax = new Vector2(-10f, 0f);
RectTransform fillAreaRect = fillAreaObj.GetComponent<RectTransform>();
fillAreaRect.anchorMin = new Vector2(0f, 0.25f);
fillAreaRect.anchorMax = new Vector2(1f, 0.75f);
fillAreaRect.anchoredPosition = new Vector2(-5f, 0f);
fillAreaRect.sizeDelta = new Vector2(-20f, 0f);
Image fillImage = fillObj.AddComponent<Image>();
fillImage.type = Image.Type.Sliced;
fillImage.color = Color.clear;
fillObj.GetComponent<RectTransform>().sizeDelta = new Vector2(10f, 0f);
RectTransform handleSlideRect = handleSlideAreaObj.GetComponent<RectTransform>();
handleSlideRect.anchorMin = new Vector2(0f, 0f);
handleSlideRect.anchorMax = new Vector2(1f, 1f);
handleSlideRect.offsetMin = new Vector2(15f, 30f);
handleSlideRect.offsetMax = new Vector2(-15f, 0f);
handleSlideRect.sizeDelta = new Vector2(-30f, -30f);
Image handleImage = handleObj.AddComponent<Image>();
handleImage.color = new Color(0.5f, 0.5f, 0.5f, 1.0f);
var handleRect = handleObj.GetComponent<RectTransform>();
handleRect.sizeDelta = new Vector2(15f, 30f);
handleRect.offsetMin = new Vector2(-13f, -28f);
handleRect.offsetMax = new Vector2(3f, -2f);
var sliderBarLayout = sliderObj.AddComponent<LayoutElement>();
sliderBarLayout.minWidth = 25;
sliderBarLayout.flexibleWidth = 0;
sliderBarLayout.minHeight = 30;
sliderBarLayout.flexibleHeight = 5000;
slider = sliderObj.AddComponent<Slider>();
slider.fillRect = fillObj.GetComponent<RectTransform>();
slider.handleRect = handleObj.GetComponent<RectTransform>();
slider.targetGraphic = handleImage;
slider.direction = Slider.Direction.BottomToTop;
UIFactory.SetDefaultColorTransitionValues(slider);
return sliderObj;
}
#endregion
}
#if MONO
public static class SliderExtensions
{
@ -175,4 +179,5 @@ public static class SliderExtensions
SetMethod.Invoke(slider, new object[] { value, invokeCallback });
}
}
#endif
#endif
}

View File

@ -4,11 +4,12 @@ using UnityEngine.UI;
using UnityExplorer.Inspectors;
using UnityExplorer.UI.Modules;
using System.IO;
//using TMPro;
using System.Reflection;
using UnityExplorer.Helpers;
using UnityExplorer.UI.Shared;
using UnityExplorer.Input;
using System;
using UnityExplorer.Config;
#if CPP
using UnityExplorer.Unstrip;
#endif
@ -20,9 +21,48 @@ namespace UnityExplorer.UI
public static GameObject CanvasRoot { get; private set; }
public static EventSystem EventSys { get; private set; }
internal static Sprite ResizeCursor { get; private set; }
internal static Font ConsoleFont { get; private set; }
internal static Sprite ResizeCursor { get; private set; }
internal static Shader BackupShader { get; private set; }
public static bool ShowMenu
{
get => s_showMenu;
set => SetShowMenu(value);
}
public static bool s_showMenu;
private static bool s_doneUIInit;
private static float s_timeSinceStartup;
internal static void CheckUIInit()
{
if (s_doneUIInit)
return;
s_timeSinceStartup += Time.deltaTime;
if (s_timeSinceStartup > 0.1f)
{
s_doneUIInit = true;
try
{
Init();
ExplorerCore.Log("Initialized UnityExplorer UI.");
if (ExplorerConfig.Instance.Hide_On_Startup)
ShowMenu = false;
// InspectorManager.Instance.Inspect(Tests.TestClass.Instance);
}
catch (Exception e)
{
ExplorerCore.LogWarning($"Exception setting up UI: {e}");
}
}
}
public static void Init()
{
LoadBundle();
@ -39,25 +79,48 @@ namespace UnityExplorer.UI
Canvas.ForceUpdateCanvases();
}
public static void OnSceneChange()
private static void SetShowMenu(bool show)
{
SceneExplorer.Instance?.OnSceneChange();
SearchPage.Instance?.OnSceneChange();
if (s_showMenu == show)
return;
s_showMenu = show;
if (CanvasRoot)
{
CanvasRoot.SetActive(show);
if (show)
ForceUnlockCursor.SetEventSystem();
else
ForceUnlockCursor.ReleaseEventSystem();
}
ForceUnlockCursor.UpdateCursorControl();
}
//public static void OnSceneChange()
//{
// SceneExplorer.Instance?.OnSceneChange();
// SearchPage.Instance?.OnSceneChange();
//}
public static void Update()
{
if (InputManager.GetKeyDown(ExplorerConfig.Instance.Main_Menu_Toggle))
ShowMenu = !ShowMenu;
if (!ShowMenu || !s_doneUIInit || !CanvasRoot)
return;
MainMenu.Instance?.Update();
if (EventSys)
{
if (EventSystem.current != EventSys)
{
ForceUnlockCursor.SetEventSystem();
}
#if CPP
// Fix for games which override the InputModule pointer events (eg, VRChat)
// Some IL2CPP games behave weird with multiple UI Input Systems, some fixes for them.
var evt = InputManager.InputPointerEvent;
if (evt != null)
{
@ -68,9 +131,7 @@ namespace UnityExplorer.UI
}
if (PanelDragger.Instance != null)
{
PanelDragger.Instance.Update();
}
for (int i = 0; i < SliderScrollbar.Instances.Count; i++)
{
@ -86,38 +147,73 @@ namespace UnityExplorer.UI
{
var input = InputFieldScroller.Instances[i];
if (input.sliderScroller.CheckDestroyed())
if (input.CheckDestroyed())
i--;
else
input.Update();
}
}
private static AssetBundle LoadExplorerUi(string id)
{
return AssetBundle.LoadFromMemory(ReadFully(typeof(ExplorerCore).Assembly.GetManifestResourceStream($"UnityExplorer.Resources.explorerui.{id}.bundle")));
}
private static byte[] ReadFully(this Stream input)
{
using (var ms = new MemoryStream())
{
byte[] buffer = new byte[81920];
int read;
while ((read = input.Read(buffer, 0, buffer.Length)) != 0)
ms.Write(buffer, 0, read);
return ms.ToArray();
}
}
private static void LoadBundle()
{
var bundlePath = ExplorerCore.EXPLORER_FOLDER + @"\explorerui.bundle";
if (File.Exists(bundlePath))
AssetBundle bundle = null;
try
{
var bundle = AssetBundle.LoadFromFile(bundlePath);
// Fix for games which don't ship with 'UI/Default' shader.
if (Graphic.defaultGraphicMaterial.shader?.name != "UI/Default")
{
ExplorerCore.Log("This game does not ship with the 'UI/Default' shader, using manual Default Shader...");
Graphic.defaultGraphicMaterial.shader = bundle.LoadAsset<Shader>("DefaultUI");
}
ResizeCursor = bundle.LoadAsset<Sprite>("cursor");
ConsoleFont = bundle.LoadAsset<Font>("CONSOLA");
ExplorerCore.Log("Loaded UI bundle");
bundle = LoadExplorerUi("modern");
}
else
catch
{
ExplorerCore.LogWarning("Could not find the ExplorerUI Bundle! It should exist at '" + bundlePath + "'");
ExplorerCore.Log("Failed to load modern ExplorerUI Bundle, falling back to legacy");
try
{
bundle = LoadExplorerUi("legacy");
}
catch
{
// ignored
}
}
if (bundle == null)
{
ExplorerCore.LogWarning("Could not load the ExplorerUI Bundle!");
return;
}
BackupShader = bundle.LoadAsset<Shader>("DefaultUI");
// Fix for games which don't ship with 'UI/Default' shader.
if (Graphic.defaultGraphicMaterial.shader?.name != "UI/Default")
{
ExplorerCore.Log("This game does not ship with the 'UI/Default' shader, using manual Default Shader...");
Graphic.defaultGraphicMaterial.shader = BackupShader;
}
ResizeCursor = bundle.LoadAsset<Sprite>("cursor");
ConsoleFont = bundle.LoadAsset<Font>("CONSOLA");
ExplorerCore.Log("Loaded UI bundle");
}
private static GameObject CreateRootCanvas()
@ -136,7 +232,7 @@ namespace UnityExplorer.UI
canvas.renderMode = RenderMode.ScreenSpaceCamera;
canvas.referencePixelsPerUnit = 100;
canvas.sortingOrder = 999;
canvas.pixelPerfect = false;
//canvas.pixelPerfect = false;
CanvasScaler scaler = rootObj.AddComponent<CanvasScaler>();
scaler.referenceResolution = new Vector2(1920, 1080);
@ -146,37 +242,5 @@ namespace UnityExplorer.UI
return rootObj;
}
public static Sprite CreateSprite(Texture2D tex, Rect size = default)
{
#if CPP
Vector2 pivot = Vector2.zero;
Vector4 border = Vector4.zero;
if (size == default)
{
size = new Rect(0, 0, tex.width, tex.height);
}
return Sprite.CreateSprite_Injected(tex, ref size, ref pivot, 100f, 0u, SpriteMeshType.Tight, ref border, false);
#else
return Sprite.Create(tex, size, Vector2.zero);
#endif
}
public static Texture2D MakeSolidTexture(Color color, int width, int height)
{
Color[] pixels = new Color[width * height];
for (int i = 0; i < pixels.Length; i++)
{
pixels[i] = color;
}
Texture2D tex = new Texture2D(width, height);
tex.SetPixels(pixels);
tex.Apply();
return tex;
}
}
}

View File

@ -8,36 +8,36 @@ namespace UnityExplorer.UI
{
public class UISyntaxHighlight
{
public const string Field_Static = "#8d8dc6";
public const string Field_Instance = "#c266ff";
public const string FIELD_STATIC = "#8d8dc6";
public const string FIELD_INSTANCE = "#c266ff";
public const string Method_Static = "#b55b02";
public const string Method_Instance = "#ff8000";
public const string METHOD_STATIC = "#b55b02";
public const string METHOD_INSTANCE = "#ff8000";
public const string Prop_Static = "#588075";
public const string Prop_Instance = "#55a38e";
public const string PROP_STATIC = "#588075";
public const string PROP_INSTANCE = "#55a38e";
public const string Class_Static = "#3a8d71";
public const string Class_Instance = "#2df7b2";
public const string CLASS_STATIC = "#3a8d71";
public const string CLASS_INSTANCE = "#2df7b2";
public const string Local = "#a6e9e9";
public const string CLASS_STRUCT = "#0fba3a";
public const string StructGreen = "#0fba3a";
public const string LOCAL_ARG = "#a6e9e9";
public static string Enum = "#92c470";
public static string CONST_VAR = "#92c470";
internal static readonly Color s_silver = new Color(0.66f, 0.66f, 0.66f);
internal static string GetClassColor(Type type)
{
if (type.IsAbstract && type.IsSealed)
return Class_Static;
return CLASS_STATIC;
else if (type.IsEnum || type.IsGenericParameter)
return Enum;
return CONST_VAR;
else if (type.IsValueType)
return StructGreen;
return CLASS_STRUCT;
else
return Class_Instance;
return CLASS_INSTANCE;
}
public static string ParseFullSyntax(Type type, bool includeNamespace, MemberInfo memberInfo = null)
@ -49,7 +49,7 @@ namespace UnityExplorer.UI
if (type.IsGenericParameter || (type.HasElementType && type.GetElementType().IsGenericParameter))
{
ret = $"<color={Enum}>{type.Name}</color>";
ret = $"<color={CONST_VAR}>{type.Name}</color>";
}
else
{
@ -134,7 +134,7 @@ namespace UnityExplorer.UI
if (allGeneric)
{
args += $"<color={Enum}>{arg.Name}</color>";
args += $"<color={CONST_VAR}>{arg.Name}</color>";
continue;
}
@ -153,30 +153,30 @@ namespace UnityExplorer.UI
if (fi.IsStatic)
{
isStatic = true;
memberColor = Field_Static;
memberColor = FIELD_STATIC;
}
else
memberColor = Field_Instance;
memberColor = FIELD_INSTANCE;
}
else if (memberInfo is MethodInfo mi)
{
if (mi.IsStatic)
{
isStatic = true;
memberColor = Method_Static;
memberColor = METHOD_STATIC;
}
else
memberColor = Method_Instance;
memberColor = METHOD_INSTANCE;
}
else if (memberInfo is PropertyInfo pi)
{
if (pi.GetAccessors(true)[0].IsStatic)
{
isStatic = true;
memberColor = Prop_Static;
memberColor = PROP_STATIC;
}
else
memberColor = Prop_Instance;
memberColor = PROP_INSTANCE;
}
return memberColor;
}

View File

@ -11,7 +11,7 @@
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
<TargetFrameworkProfile />
<OutputPath>..\Release\Explorer.MelonLoader.Il2Cpp\</OutputPath>
<OutputPath>..\Release\UnityExplorer.MelonLoader.Il2Cpp\</OutputPath>
<DefineConstants>
</DefineConstants>
<IsCpp>false</IsCpp>
@ -25,17 +25,9 @@
<Prefer32Bit>false</Prefer32Bit>
<RootNamespace>UnityExplorer</RootNamespace>
<!-- Set this to the BepInEx Il2Cpp Game folder, without the ending '\' character. -->
<BIECppGameFolder>D:\Steam\steamapps\common\Outward</BIECppGameFolder>
<!-- Set this to the BepInEx Mono Game folder, without the ending '\' character. -->
<BIEMonoGameFolder>D:\source\Unity Projects\Test\_BUILD_MONO</BIEMonoGameFolder>
<!-- Set this to the BepInEx Mono Managed folder, without the ending '\' character. -->
<BIEMonoManagedFolder>D:\source\Unity Projects\Test\_BUILD_MONO\Test_Data\Managed</BIEMonoManagedFolder>
<BIECppGameFolder>E:\source\Unity Projects\Test\_BUILD</BIECppGameFolder>
<!-- Set this to the MelonLoader Il2Cpp Game folder, without the ending '\' character. -->
<MLCppGameFolder>D:\Steam\steamapps\common\VRChat</MLCppGameFolder>
<!-- Set this to the MelonLoader Mono Game folder, without the ending '\' character. -->
<MLMonoGameFolder>D:\source\Unity Projects\Test\_BUILD_MONO</MLMonoGameFolder>
<!-- Set this to the MelonLoader Mono Managed folder, without the ending '\' character. -->
<MLMonoManagedFolder>D:\source\Unity Projects\Test\_BUILD_MONO\Test_Data\Managed</MLMonoManagedFolder>
<MLCppGameFolder>E:\source\Unity Projects\Test\_BUILD</MLCppGameFolder>
<NuGetPackageImportStamp>
</NuGetPackageImportStamp>
</PropertyGroup>
@ -46,7 +38,6 @@
<AssemblyName>UnityExplorer.ML.IL2CPP</AssemblyName>
<IsCpp>true</IsCpp>
<IsMelonLoader>true</IsMelonLoader>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_ML_Mono|AnyCPU' ">
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
@ -56,27 +47,54 @@
<Prefer32Bit>false</Prefer32Bit>
<IsCpp>false</IsCpp>
<IsMelonLoader>true</IsMelonLoader>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_BIE_Cpp|AnyCPU' ">
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<OutputPath>..\Release\UnityExplorer.BepInEx.Il2Cpp\</OutputPath>
<DefineConstants>CPP,BIE</DefineConstants>
<DefineConstants>CPP,BIE,BIE6</DefineConstants>
<AssemblyName>UnityExplorer.BIE.IL2CPP</AssemblyName>
<IsCpp>true</IsCpp>
<IsMelonLoader>false</IsMelonLoader>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_BIE_Mono|AnyCPU' ">
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release_BIE6_Mono|AnyCPU' ">
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<OutputPath>..\Release\UnityExplorer.BepInEx.Mono\</OutputPath>
<DefineConstants>MONO,BIE</DefineConstants>
<AssemblyName>UnityExplorer.BIE.Mono</AssemblyName>
<OutputPath>..\Release\UnityExplorer.BepInEx6.Mono\</OutputPath>
<DefineConstants>MONO,BIE,BIE6</DefineConstants>
<AssemblyName>UnityExplorer.BIE6.Mono</AssemblyName>
<IsCpp>false</IsCpp>
<IsMelonLoader>false</IsMelonLoader>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release_BIE5_Mono|AnyCPU'">
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<OutputPath>..\Release\UnityExplorer.BepInEx5.Mono\</OutputPath>
<DefineConstants>MONO,BIE,BIE5</DefineConstants>
<AssemblyName>UnityExplorer.BIE5.Mono</AssemblyName>
<IsCpp>false</IsCpp>
<IsMelonLoader>false</IsMelonLoader>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release_STANDALONE_Mono|AnyCPU'">
<TargetFrameworkVersion>v3.5</TargetFrameworkVersion>
<OutputPath>..\Release\UnityExplorer.Standalone.Mono\</OutputPath>
<DefineConstants>MONO,STANDALONE</DefineConstants>
<AssemblyName>UnityExplorer.STANDALONE.Mono</AssemblyName>
<IsCpp>false</IsCpp>
<IsMelonLoader>false</IsMelonLoader>
<IsStandalone>true</IsStandalone>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'Release_STANDALONE_Cpp|AnyCPU'">
<TargetFrameworkVersion>v4.7.2</TargetFrameworkVersion>
<OutputPath>..\Release\UnityExplorer.Standalone.Il2Cpp\</OutputPath>
<DefineConstants>CPP,STANDALONE</DefineConstants>
<AssemblyName>UnityExplorer.STANDALONE.IL2CPP</AssemblyName>
<IsCpp>true</IsCpp>
<IsMelonLoader>false</IsMelonLoader>
<IsStandalone>true</IsStandalone>
</PropertyGroup>
<ItemGroup>
<Reference Include="INIFileParser, Version=2.5.2.0, Culture=neutral, PublicKeyToken=79af7b307b65cf3c, processorArchitecture=MSIL">
<HintPath>packages\ini-parser.2.5.2\lib\net20\INIFileParser.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
@ -102,13 +120,13 @@
</ItemGroup>
<!-- MelonLoader Mono refs -->
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='true|false'">
<Reference Include="MelonLoader.ModHandler">
<HintPath>$(MLMonoGameFolder)\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
<Reference Include="MelonLoader">
<HintPath>..\lib\MelonLoader.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<!-- BepInEx Mono refs -->
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='false|false'">
<!-- BepInEx 5 Mono refs -->
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)|$(Configuration)'=='false|false|Release_BIE5_Mono'">
<Reference Include="BepInEx">
<HintPath>..\lib\BepInEx.dll</HintPath>
<Private>False</Private>
@ -118,10 +136,32 @@
<Private>False</Private>
</Reference>
</ItemGroup>
<!-- BepInEx 6 Mono refs -->
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)|$(Configuration)'=='false|false|Release_BIE6_Mono'">
<Reference Include="BepInEx">
<HintPath>..\lib\BepInEx.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="BepInEx.Unity">
<HintPath>..\lib\BepInEx.Unity.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="0Harmony">
<HintPath>..\lib\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<!-- Standalone refs -->
<ItemGroup Condition="'$(IsStandalone)'=='true'">
<Reference Include="0Harmony">
<HintPath>..\lib\0Harmony.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<!-- MelonLoader Il2Cpp refs -->
<ItemGroup Condition="'$(IsMelonLoader)|$(IsCpp)'=='true|true'">
<Reference Include="MelonLoader.ModHandler">
<HintPath>$(MLCppGameFolder)\MelonLoader\MelonLoader.ModHandler.dll</HintPath>
<Reference Include="MelonLoader">
<HintPath>$(MLCppGameFolder)\MelonLoader\MelonLoader.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="UnhollowerBaseLib">
@ -221,7 +261,8 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Helpers\EventHelper.cs" />
<Compile Include="Loader\ExplorerBepIn6Plugin.cs" />
<Compile Include="Loader\ExplorerStandalone.cs" />
<Compile Include="Inspectors\MouseInspector.cs" />
<Compile Include="Inspectors\Reflection\CacheObject\CacheEnumerated.cs" />
<Compile Include="Inspectors\Reflection\CacheObject\CacheField.cs" />
@ -231,10 +272,10 @@
<Compile Include="Inspectors\Reflection\CacheObject\CacheProperty.cs" />
<Compile Include="Inspectors\Reflection\CacheObject\CacheObjectBase.cs" />
<Compile Include="Helpers\Texture2DHelpers.cs" />
<Compile Include="Config\ModConfig.cs" />
<Compile Include="Config\ExplorerConfig.cs" />
<Compile Include="ExplorerCore.cs" />
<Compile Include="ExplorerBepInPlugin.cs" />
<Compile Include="ExplorerMelonMod.cs" />
<Compile Include="Loader\ExplorerBepIn5Plugin.cs" />
<Compile Include="Loader\ExplorerMelonMod.cs" />
<Compile Include="Helpers\ReflectionHelpers.cs" />
<Compile Include="Helpers\UnityHelpers.cs" />
<Compile Include="Inspectors\GameObjects\ChildList.cs" />
@ -248,6 +289,10 @@
<Compile Include="Inspectors\Reflection\InteractiveValue\InteractiveNumber.cs" />
<Compile Include="Inspectors\Reflection\InteractiveValue\InteractiveString.cs" />
<Compile Include="Inspectors\Reflection\InteractiveValue\InteractiveUnityStruct.cs" />
<Compile Include="Loader\IExplorerLoader.cs" />
<Compile Include="Runtime\Il2Cpp\Il2CppProvider.cs" />
<Compile Include="Runtime\RuntimeProvider.cs" />
<Compile Include="Runtime\Mono\MonoProvider.cs" />
<Compile Include="UI\ForceUnlockCursor.cs" />
<Compile Include="Input\IHandleInput.cs" />
<Compile Include="Tests\Tests.cs" />
@ -297,6 +342,7 @@
<Compile Include="Unstrip\SceneUnstrip.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="UI\UIFactory.cs" />
<EmbeddedResource Include="Resources\*" />
</ItemGroup>
<ItemGroup>
<None Include="ILRepack.targets" />

View File

@ -8,19 +8,28 @@ EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Release_BIE_Cpp|Any CPU = Release_BIE_Cpp|Any CPU
Release_BIE_Mono|Any CPU = Release_BIE_Mono|Any CPU
Release_BIE5_Mono|Any CPU = Release_BIE5_Mono|Any CPU
Release_BIE6_Mono|Any CPU = Release_BIE6_Mono|Any CPU
Release_ML_Cpp|Any CPU = Release_ML_Cpp|Any CPU
Release_ML_Mono|Any CPU = Release_ML_Mono|Any CPU
Release_STANDALONE_Cpp|Any CPU = Release_STANDALONE_Cpp|Any CPU
Release_STANDALONE_Mono|Any CPU = Release_STANDALONE_Mono|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Cpp|Any CPU.ActiveCfg = Release_BIE_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Cpp|Any CPU.Build.0 = Release_BIE_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Mono|Any CPU.ActiveCfg = Release_BIE_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE_Mono|Any CPU.Build.0 = Release_BIE_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE5_Mono|Any CPU.ActiveCfg = Release_BIE5_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE5_Mono|Any CPU.Build.0 = Release_BIE5_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE6_Mono|Any CPU.ActiveCfg = Release_BIE6_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_BIE6_Mono|Any CPU.Build.0 = Release_BIE6_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Cpp|Any CPU.ActiveCfg = Release_ML_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Cpp|Any CPU.Build.0 = Release_ML_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Mono|Any CPU.ActiveCfg = Release_ML_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_ML_Mono|Any CPU.Build.0 = Release_ML_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Cpp|Any CPU.ActiveCfg = Release_STANDALONE_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Cpp|Any CPU.Build.0 = Release_STANDALONE_Cpp|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Mono|Any CPU.ActiveCfg = Release_STANDALONE_Mono|Any CPU
{B21DBDE3-5D6F-4726-93AB-CC3CC68BAE7D}.Release_STANDALONE_Mono|Any CPU.Build.0 = Release_STANDALONE_Mono|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -24,6 +24,17 @@ namespace UnityExplorer.Unstrip
return new AssetBundle(ptr);
}
private delegate IntPtr d_LoadFromMemory(IntPtr binary, uint crc);
public static AssetBundle LoadFromMemory(byte[] binary, uint crc = 0)
{
var iCall = ICallHelper.GetICall<d_LoadFromMemory>("UnityEngine.AssetBundle::LoadFromMemory_Internal");
var ptr = iCall(((Il2CppStructArray<byte>) binary).Pointer, crc);
return new AssetBundle(ptr);
}
// ~~~~~~~~~~~~ Instance ~~~~~~~~~~~~

View File

@ -10,6 +10,16 @@ namespace UnityExplorer.Unstrip
{
public static class ImageConversionUnstrip
{
// LoadImage helper from a filepath
public static bool LoadImage(Texture2D tex, string filePath, bool markNonReadable)
{
if (!File.Exists(filePath))
return false;
return tex.LoadImage(File.ReadAllBytes(filePath), markNonReadable);
}
#if CPP
// byte[] ImageConversion.EncodeToPNG(this Texture2D image);
@ -17,8 +27,12 @@ namespace UnityExplorer.Unstrip
public static byte[] EncodeToPNG(this Texture2D tex)
{
IntPtr ptr = ICallHelper.GetICall<d_EncodeToPNG>("UnityEngine.ImageConversion::EncodeToPNG")
.Invoke(tex.Pointer);
var iCall = ICallHelper.GetICall<d_EncodeToPNG>("UnityEngine.ImageConversion::EncodeToPNG");
IntPtr ptr = iCall.Invoke(tex.Pointer);
if (ptr == IntPtr.Zero)
return null;
return new Il2CppStructArray<byte>(ptr);
}
@ -29,28 +43,40 @@ namespace UnityExplorer.Unstrip
public static bool LoadImage(this Texture2D tex, byte[] data, bool markNonReadable)
{
Il2CppStructArray<byte> il2cppArray = new Il2CppStructArray<byte>(data.Length);
for (int i = 0; i < data.Length; i++)
il2cppArray[i] = data[i];
var il2cppArray = (Il2CppStructArray<byte>)data;
bool ret = ICallHelper.GetICall<d_LoadImage>("UnityEngine.ImageConversion::LoadImage")
.Invoke(tex.Pointer, il2cppArray.Pointer, markNonReadable);
var iCall = ICallHelper.GetICall<d_LoadImage>("UnityEngine.ImageConversion::LoadImage");
return iCall.Invoke(tex.Pointer, il2cppArray.Pointer, markNonReadable);
}
return ret;
// Sprite Sprite.Create
internal delegate IntPtr d_CreateSprite(IntPtr texture, ref Rect rect, ref Vector2 pivot, float pixelsPerUnit,
uint extrude, int meshType, ref Vector4 border, bool generateFallbackPhysicsShape);
public static Sprite CreateSprite(Texture texture, Rect rect, Vector2 pivot, float pixelsPerUnit, uint extrude, Vector4 border)
{
var iCall = ICallHelper.GetICall<d_CreateSprite>("UnityEngine.Sprite::CreateSprite_Injected");
var ptr = iCall.Invoke(texture.Pointer, ref rect, ref pivot, pixelsPerUnit, extrude, 1, ref border, false);
if (ptr == IntPtr.Zero)
return null;
else
return new Sprite(ptr);
}
#endif
// Helper for LoadImage from filepath
// Simpler CreateSprite helper
public static bool LoadImage(Texture2D tex, string filePath, bool markNonReadable)
public static Sprite CreateSprite(Texture2D texture)
{
if (!File.Exists(filePath))
{
return false;
}
byte[] data = File.ReadAllBytes(filePath);
return tex.LoadImage(data, markNonReadable);
#if CPP
return CreateSprite(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero, 100f, 0u, Vector4.zero);
#else
return Sprite.Create(texture, new Rect(0, 0, texture.width, texture.height), Vector2.zero);
#endif
}
}
}

View File

@ -3,24 +3,27 @@ using Mono.CSharp;
using UnityExplorer.Helpers;
#if CPP
using UnhollowerBaseLib;
using UnhollowerRuntimeLib;
#endif
namespace UnityExplorer.Unstrip
{
public class ResourcesUnstrip
{
public static UnityEngine.Object[] FindObjectsOfTypeAll(Type type)
{
#if MONO
return UnityEngine.Resources.FindObjectsOfTypeAll(type);
#else
var iCall = ICallHelper.GetICall<d_FindObjectsOfTypeAll>("UnityEngine.Resources::FindObjectsOfTypeAll");
var cppType = Il2CppType.From(type);
return new Il2CppReferenceArray<UnityEngine.Object>(iCall.Invoke(cppType.Pointer));
#endif
}
#if CPP
internal delegate IntPtr d_FindObjectsOfTypeAll(IntPtr type);
public static UnityEngine.Object[] FindObjectsOfTypeAll(Il2CppSystem.Type type)
{
var iCall = ICallHelper.GetICall<d_FindObjectsOfTypeAll>("UnityEngine.Resources::FindObjectsOfTypeAll");
return new Il2CppReferenceArray<UnityEngine.Object>(iCall.Invoke(type.Pointer));
}
#else
public static UnityEngine.Object[] FindObjectsOfTypeAll(Type type) => UnityEngine.Resources.FindObjectsOfTypeAll(type);
#endif
}
}

View File

@ -3,36 +3,25 @@ using UnityExplorer.Helpers;
using UnityEngine;
using UnityEngine.SceneManagement;
using UnityExplorer.Inspectors;
using System.Reflection;
namespace UnityExplorer.Unstrip
{
public class SceneUnstrip
public static class SceneUnstrip
{
#if MONO
public static GameObject[] GetRootGameObjects(Scene scene) => scene.GetRootGameObjects();
//public static GameObject[] GetRootGameObjects(int handle)
//{
// Scene scene = default;
// if (handle == SceneExplorer.DontDestroyHandle)
// scene = SceneExplorer.DontDestroyObject.scene;
// else
// {
// for (int i = 0; i < SceneManager.sceneCount; i++)
// {
// var iscene = SceneManager.GetSceneAt(i);
// if (iscene.handle == handle)
// scene = iscene;
// }
// }
// if (scene != default && scene.handle != -1)
// return scene.GetRootGameObjects();
// return new GameObject[0];
//}
private static readonly FieldInfo fi_Scene_handle = typeof(Scene).GetField("m_Handle", ReflectionHelpers.CommonFlags);
#endif
public static int GetHandle(this Scene scene)
{
#if CPP
return scene.handle;
#else
return (int)fi_Scene_handle.GetValue(scene);
#endif
}
#if CPP
//Scene.GetRootGameObjects();
@ -43,13 +32,16 @@ namespace UnityExplorer.Unstrip
public static GameObject[] GetRootGameObjects(int handle)
{
if (handle == -1)
{
return new GameObject[0];
}
Il2CppSystem.Collections.Generic.List<GameObject> list = new Il2CppSystem.Collections.Generic.List<GameObject>(GetRootCount(handle));
int count = GetRootCount(handle);
d_GetRootGameObjects iCall = ICallHelper.GetICall<d_GetRootGameObjects>("UnityEngine.SceneManagement.Scene::GetRootGameObjectsInternal");
if (count < 1)
return new GameObject[0];
var list = new Il2CppSystem.Collections.Generic.List<GameObject>(count);
var iCall = ICallHelper.GetICall<d_GetRootGameObjects>("UnityEngine.SceneManagement.Scene::GetRootGameObjectsInternal");
iCall.Invoke(handle, list.Pointer);
@ -58,14 +50,14 @@ namespace UnityExplorer.Unstrip
//Scene.rootCount;
internal delegate int GetRootCountInternal_delegate(int handle);
internal delegate int d_GetRootCountInternal(int handle);
public static int GetRootCount(Scene scene) => GetRootCount(scene.handle);
public static int GetRootCount(int handle)
{
GetRootCountInternal_delegate iCall = ICallHelper.GetICall<GetRootCountInternal_delegate>("UnityEngine.SceneManagement.Scene::GetRootCountInternal");
return iCall.Invoke(handle);
return ICallHelper.GetICall<d_GetRootCountInternal>("UnityEngine.SceneManagement.Scene::GetRootCountInternal")
.Invoke(handle);
}
#endif
}

View File

@ -1,4 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="ILRepack.Lib.MSBuild.Task" version="2.0.18.1" targetFramework="net472" />
<package id="ini-parser" version="2.5.2" targetFramework="net35" />
</packages>